diff --git a/README.md b/README.md index a67f62790..5f65679bd 100644 --- a/README.md +++ b/README.md @@ -55,7 +55,7 @@ The latest ResilientDB documentation, including a programming guide, is availabl ## OS Requirements -Ubuntu 20.* +Ubuntu 20+ --- @@ -78,29 +78,219 @@ Build Interactive Tools: bazel build service/tools/kv/api_tools/kv_service_tools -Run tools to set a value by a key (for example, set the value with key "test" and value "test_value"): +## Functions ## +ResilientDB supports two types of functions: version-based and non-version-based. +Version-based functions will leverage versions to protect each update, versions must be obtained before updating a key. - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config set test test_value - -You will see the following result if successful: +***Note***: Version-based functions are not compatible with non-version-based functions. Do not use both in your applications. - client set ret = 0 +We show the functions below and show how to use [kv_service_tools](service/tools/kv/api_tools/kv_service_tools.cpp) to test the function. -Run tools to get value by a key (for example, get the value with key "test"): +### Version-Based Functions ### +#### Get #### +Obtain the value of `key` with a specific version `v`. - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config get test - -You will see the following result if successful: + kv_service_tools --config config_file --cmd get_with_version --key key --version v + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get_with_version | +| key | the key you want to obtain | +| version | the version you want to obtain. (If the `v` is 0, it will return the latest version | + + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get_with_version --key key1 --version 0 + +Results: +> get key = key1, value = value: "v2" +> version: 2 + +#### Set #### +Set `value` to the key `key` based on version `v`. + + kv_service_tools --config config_file --cmd set_with_version --key key --version v --value value + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | set_with_version | +| key | the key you want to set | +| version | the version you have obtained. (If the version has been changed during the update, the transaction will be ignored) | +| value | the new value | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd set_with_version --key key1 --version 0 --value v1 + +Results: +> set key = key1, value = v3, version = 2 done, ret = 0 +> +> current value = value: "v3" +> version: 3 + +#### Get Key History #### +Obtain the update history of key `key` within the versions [`v1`, `v2`]. + + kv_service_tools --config config_file --cmd get_history --key key --min_version v1 --max_version v2 + + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get_history | +| key | the key you want to obtain | +| min_version | the minimum version you want to obtain | +| max_version | the maximum version you want to obtain | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get_history --key key1 --min_version 1 --max_version 2 + +Results: + +> get history key = key1, min version = 1, max version = 2
+> value =
+> item {
+>   key: "key1"
+>   value_info {
+>    value: "v1"
+>    version: 2
+>  }
+> }
+> item {
+>   key: "key1"
+>   value_info {
+>    value: "v0"
+>    version: 1
+>  }
+> } + +#### Get Top #### +Obtain the recent `top_number` history of the key `key`. + + kv_service_tools --config config_path --cmd get_top --key key --top top_number + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get_top | +| key | the key you want to obtain | +| top | the number of the recent updates | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get_top --key key1 --top 1 + +Results: + +>key = key1, top 1
+> value =
+> item {
+> key: "key1"
+>  value_info {
+>    value: "v2"
+>    version: 3
+>  }
+>} + +#### Get Key Range #### +Obtain the values of the keys in the ranges [`key1`, `key2`]. Do not use this function in your practice code - client get value = test_value + kv_service_tools --config config_file --cmd get_key_range_with_version --min_key key1 --max_key key2 -Run tools to get all values that have been set: +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get_key_range_with_version | +| min_key | the minimum key | +| max_key | the maximum key | - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config getallvalues +Example: -You will see the following result if successful: + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get_key_range_with_version --min_key key1 --max_key key3 + +Results: + +>min key = key1 max key = key2
+> getrange value =
+> item {
+>   key: "key1"
+>   value_info {
+>    value: "v0"
+>    version: 1
+>   }
+> }
+> item {
+>   key: "key2"
+>   value_info {
+>    value: "v1"
+>    version: 1
+>   }
+>} + + +### Non-Version-Based Function ### +#### Set ##### +Set `value` to the key `key`. + + kv_service_tools --config config_file --cmd set --key key --value value + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | set | +| key | the key you want to set | +| value | the new value | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd set --key key1 --value value1 + +Results: +> set key = key1, value = v1, done, ret = 0 + +#### Get #### +Obtain the value of `key`. + + kv_service_tools --config config_file --cmd get --key key + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get | +| key | the key you want to obtain | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get --key key1 + +Results: +> get key = key1, value = "v2" + + +#### Get Key Range #### +Obtain the values of the keys in the ranges [`key1`, `key2`]. Do not use this function in your practice code + + kv_service_tools --config config_path --cmd get_key_range --min_key key1 --max_key key2 + +| parameters | descriptions | +| ---- | ---- | +| config | the path of the client config which points to the db entrance | +| cmd | get_key_range | +| min_key | the minimum key | +| max_key | the maximum key | + +Example: + + bazel-bin/service/tools/kv/api_tools/kv_service_tools --config service/tools/config/interface/service.config --cmd get_key_range --min_key key1 --max_key key3 + +Results: +> getrange min key = key1, max key = key3
+> value = [v3,v2,v1] - client getallvalues value = [test_value] ## Deployment Script @@ -143,14 +333,4 @@ We also provide access to a [deployment script](https://github.com/resilientdb/r docker exec -it myserver bash ``` - Verify the functionality of the service by performing set and get operations: - - - Set a test value: - ```shell - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config set test test_value - ``` - - - Retrieve the test value: - ``` - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config get test - ``` + Verify the functionality of the service by performing set and get operations provided above [functions](README.md#functions). diff --git a/benchmark/protocols/pbft/BUILD b/benchmark/protocols/pbft/BUILD index 456d6d4c0..659280d97 100644 --- a/benchmark/protocols/pbft/BUILD +++ b/benchmark/protocols/pbft/BUILD @@ -6,6 +6,7 @@ cc_binary( name = "kv_server_performance", srcs = ["kv_server_performance.cpp"], deps = [ + "//chain/storage:memory_db", "//executor/kv:kv_executor", "//platform/config:resdb_config_utils", "//platform/consensus/ordering/pbft:consensus_manager_pbft", diff --git a/benchmark/protocols/pbft/kv_server_performance.cpp b/benchmark/protocols/pbft/kv_server_performance.cpp index 8efd5b932..2b7554e30 100644 --- a/benchmark/protocols/pbft/kv_server_performance.cpp +++ b/benchmark/protocols/pbft/kv_server_performance.cpp @@ -25,7 +25,7 @@ #include -#include "chain/state/chain_state.h" +#include "chain/storage/memory_db.h" #include "executor/kv/kv_executor.h" #include "platform/config/resdb_config_utils.h" #include "platform/consensus/ordering/pbft/consensus_manager_pbft.h" @@ -34,6 +34,7 @@ #include "proto/kv/kv.pb.h" using namespace resdb; +using namespace resdb::storage; void ShowUsage() { printf(" [logging_dir]\n"); @@ -69,7 +70,7 @@ int main(int argc, char** argv) { config->RunningPerformance(true); auto performance_consens = std::make_unique( - *config, std::make_unique(std::make_unique())); + *config, std::make_unique(std::make_unique())); performance_consens->SetupPerformanceDataFunc([]() { KVRequest request; request.set_cmd(KVRequest::SET); diff --git a/chain/state/BUILD b/chain/state/BUILD index 900212ddc..5bd5d1181 100644 --- a/chain/state/BUILD +++ b/chain/state/BUILD @@ -5,8 +5,8 @@ cc_library( srcs = ["chain_state.cpp"], hdrs = ["chain_state.h"], deps = [ - "//chain/storage", "//common:comm", + "//platform/proto:resdb_cc_proto", ], ) diff --git a/chain/state/chain_state.cpp b/chain/state/chain_state.cpp index 98a82e6b6..44c2cb202 100644 --- a/chain/state/chain_state.cpp +++ b/chain/state/chain_state.cpp @@ -29,64 +29,22 @@ namespace resdb { -ChainState::ChainState(std::unique_ptr storage) - : storage_(std::move(storage)) {} +ChainState::ChainState() : max_seq_(0) {} -Storage* ChainState::GetStorage() { - return storage_ ? storage_.get() : nullptr; -} - -int ChainState::SetValue(const std::string& key, const std::string& value) { - if (storage_) { - return storage_->SetValue(key, value); +Request* ChainState::Get(uint64_t seq) { + std::unique_lock lk(mutex_); + if (data_.find(seq) == data_.end()) { + return nullptr; } - kv_map_[key] = value; - return 0; + return data_[seq].get(); } -std::string ChainState::GetValue(const std::string& key) { - if (storage_) { - return storage_->GetValue(key); - } - auto search = kv_map_.find(key); - if (search != kv_map_.end()) - return search->second; - else { - return ""; - } +void ChainState::Put(std::unique_ptr request) { + std::unique_lock lk(mutex_); + max_seq_ = request->seq(); + data_[max_seq_] = std::move(request); } -std::string ChainState::GetAllValues(void) { - if (storage_) { - return storage_->GetAllValues(); - } - std::string values = "["; - bool first_iteration = true; - for (auto kv : kv_map_) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(kv.second); - } - values.append("]"); - return values; -} - -std::string ChainState::GetRange(const std::string& min_key, - const std::string& max_key) { - if (storage_) { - return storage_->GetRange(min_key, max_key); - } - std::string values = "["; - bool first_iteration = true; - for (auto kv : kv_map_) { - if (kv.first >= min_key && kv.first <= max_key) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(kv.second); - } - } - values.append("]"); - return values; -} +uint64_t ChainState::GetMaxSeq() { return max_seq_; } } // namespace resdb diff --git a/chain/state/chain_state.h b/chain/state/chain_state.h index e2671ab59..e97ed62aa 100644 --- a/chain/state/chain_state.h +++ b/chain/state/chain_state.h @@ -25,26 +25,24 @@ #pragma once -#include +#include #include -#include "chain/storage/storage.h" +#include "platform/proto/resdb.pb.h" namespace resdb { class ChainState { public: - ChainState(std::unique_ptr storage = nullptr); - int SetValue(const std::string& key, const std::string& value); - std::string GetValue(const std::string& key); - std::string GetAllValues(void); - std::string GetRange(const std::string& min_key, const std::string& max_key); - - Storage* GetStorage(); + ChainState(); + Request* Get(uint64_t seq); + void Put(std::unique_ptr request); + uint64_t GetMaxSeq(); private: - std::unique_ptr storage_ = nullptr; - std::unordered_map kv_map_; + std::mutex mutex_; + std::unordered_map > data_; + std::atomic max_seq_; }; } // namespace resdb diff --git a/chain/state/chain_state_test.cpp b/chain/state/chain_state_test.cpp index ed745a4ac..c0178fc8a 100644 --- a/chain/state/chain_state_test.cpp +++ b/chain/state/chain_state_test.cpp @@ -28,25 +28,42 @@ #include #include +#include "common/test/test_macros.h" + namespace resdb { namespace { -TEST(KVServerExecutorTest, SetValue) { - ChainState state; +using ::resdb::testing::EqualsProto; +using ::testing::Pointee; + +TEST(ChainStateTest, GetEmptyValue) { + ChainState db; + EXPECT_EQ(db.Get(1), nullptr); +} - EXPECT_EQ(state.GetAllValues(), "[]"); - EXPECT_EQ(state.SetValue("test_key", "test_value"), 0); - EXPECT_EQ(state.GetValue("test_key"), "test_value"); +TEST(ChainStateTest, GetValue) { + Request request; + request.set_seq(1); + request.set_data("test"); - // GetAllValues and GetRange may be out of order for in-memory, so we test up to - // 1 key-value pair - EXPECT_EQ(state.GetAllValues(), "[test_value]"); - EXPECT_EQ(state.GetRange("a", "z"), "[test_value]"); + ChainState db; + db.Put(std::make_unique(request)); + EXPECT_THAT(db.Get(1), Pointee(EqualsProto(request))); } -TEST(KVServerExecutorTest, GetValue) { - ChainState state; - EXPECT_EQ(state.GetValue("test_key"), ""); +TEST(ChainStateTest, GetSecondValue) { + Request request; + request.set_seq(1); + request.set_data("test"); + + ChainState db; + db.Put(std::make_unique(request)); + + request.set_seq(1); + request.set_data("test_1"); + db.Put(std::make_unique(request)); + + EXPECT_THAT(db.Get(1), Pointee(EqualsProto(request))); } } // namespace diff --git a/chain/storage/BUILD b/chain/storage/BUILD index 0a0c8e7ff..57904291c 100644 --- a/chain/storage/BUILD +++ b/chain/storage/BUILD @@ -16,55 +16,49 @@ cc_library( ) cc_library( - name = "res_leveldb", - srcs = ["res_leveldb.cpp"], - hdrs = ["res_leveldb.h"], + name = "memory_db", + srcs = ["memory_db.cpp"], + hdrs = ["memory_db.h"], deps = [ ":storage", "//common:comm", - "//platform/proto:replica_info_cc_proto", - "//third_party:leveldb", ], ) -cc_test( - name = "res_leveldb_test", - srcs = ["res_leveldb_test.cpp"], +cc_library( + name = "leveldb", + srcs = ["leveldb.cpp"], + hdrs = ["leveldb.h"], deps = [ - ":res_leveldb", - "//common/test:test_main", + ":storage", + "//chain/storage/proto:kv_cc_proto", + "//chain/storage/proto:leveldb_config_cc_proto", + "//common:comm", + "//third_party:leveldb", ], ) cc_library( - name = "res_rocksdb", - srcs = ["res_rocksdb.cpp"], - hdrs = ["res_rocksdb.h"], + name = "rocksdb", + srcs = ["rocksdb.cpp"], + hdrs = ["rocksdb.h"], tags = ["manual"], deps = [ ":storage", + "//chain/storage/proto:kv_cc_proto", + "//chain/storage/proto:rocksdb_config_cc_proto", "//common:comm", - "//platform/proto:replica_info_cc_proto", "//third_party:rocksdb", ], ) cc_test( - name = "res_rocksdb_test", - srcs = ["res_rocksdb_test.cpp"], - tags = ["manual"], + name = "kv_storage_test", + srcs = ["kv_storage_test.cpp"], deps = [ - ":res_rocksdb", + ":leveldb", + ":memory_db", + ":rocksdb", "//common/test:test_main", ], ) - -cc_library( - name = "txn_memory_db", - srcs = ["txn_memory_db.cpp"], - hdrs = ["txn_memory_db.h"], - deps = [ - "//common:comm", - "//platform/proto:resdb_cc_proto", - ], -) diff --git a/chain/storage/README.md b/chain/storage/README.md deleted file mode 100644 index 033b0517b..000000000 --- a/chain/storage/README.md +++ /dev/null @@ -1,13 +0,0 @@ -# Durable layer -## How to use -Look at [proto/durable.proto](https://github.com/msadoghi/nexres/blob/master/proto/durable.proto) and [proto/replica_info.proto](https://github.com/msadoghi/nexres/blob/master/proto/replica_info.proto) for how to format the durability settings. - -A config file for 4 replicas on localhost can be found at [config/example/kv_config.config](https://github.com/msadoghi/nexres/blob/master/example/kv_config.config). - -## Warning - -If "path" is not set in the durability settings for RocksDB or LevelDB then default paths will be generated in the /tmp/ folder, which is cleared whenever the machine is shut down. - -If you are testing Nexres on your local machine using localhost ip, then make sure to set generate_unique_pathnames to true, because multiple processes are not allowed to open up the same RocksDB/LevelDB directory at the same time. When generate_unique_pathnames is set to true, the durable layer uses the cert file names to generate separate directories for each port of the localhost ip. - -For example, the process hosting a server with cert_1.cert will append "1" to its directory path. If the cert file name does not contain a number and generate_unique_pathnames is set, "0" will be appended to the path. diff --git a/chain/storage/kv_storage_test.cpp b/chain/storage/kv_storage_test.cpp new file mode 100644 index 000000000..b1428b771 --- /dev/null +++ b/chain/storage/kv_storage_test.cpp @@ -0,0 +1,238 @@ +/* + * Copyright (c) 2019-2022 ExpoLab, UC Davis + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include +#include +#include + +#include + +#include "chain/storage/leveldb.h" +#include "chain/storage/memory_db.h" +#include "chain/storage/rocksdb.h" + +namespace resdb { +namespace storage { +namespace { + +enum StorageType { + MEM = 0, + LEVELDB = 1, + ROCKSDB = 2, +}; + +class KVStorageTest : public ::testing::TestWithParam { + protected: + KVStorageTest() { + StorageType t = GetParam(); + switch (t) { + case MEM: + storage = NewMemoryDB(); + break; + case LEVELDB: + Reset(); + storage = NewResLevelDB(path_); + break; + case ROCKSDB: + Reset(); + storage = NewResRocksDB(path_); + break; + } + } + + private: + void Reset() { std::filesystem::remove_all(path_.c_str()); } + + protected: + std::unique_ptr storage; + std::string path_ = "/tmp/leveldb_test"; +}; + +TEST_P(KVStorageTest, SetValue) { + EXPECT_EQ(storage->SetValue("test_key", "test_value"), 0); + EXPECT_EQ(storage->GetValue("test_key"), "test_value"); +} + +TEST_P(KVStorageTest, GetValue) { + EXPECT_EQ(storage->GetValue("test_key"), ""); +} + +TEST_P(KVStorageTest, GetEmptyValueWithVersion) { + EXPECT_EQ(storage->GetValueWithVersion("test_key", 0), + std::make_pair(std::string(""), 0)); +} + +TEST_P(KVStorageTest, SetValueWithVersion) { + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value", 1), -2); + + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value", 0), 0); + + EXPECT_EQ(storage->GetValueWithVersion("test_key", 0), + std::make_pair(std::string("test_value"), 1)); + EXPECT_EQ(storage->GetValueWithVersion("test_key", 1), + std::make_pair(std::string("test_value"), 1)); + + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value_v2", 2), -2); + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value_v2", 1), 0); + + EXPECT_EQ(storage->GetValueWithVersion("test_key", 0), + std::make_pair(std::string("test_value_v2"), 2)); + + EXPECT_EQ(storage->GetValueWithVersion("test_key", 1), + std::make_pair(std::string("test_value"), 1)); + + EXPECT_EQ(storage->GetValueWithVersion("test_key", 2), + std::make_pair(std::string("test_value_v2"), 2)); + + EXPECT_EQ(storage->GetValueWithVersion("test_key", 3), + std::make_pair(std::string("test_value_v2"), 2)); +} + +TEST_P(KVStorageTest, GetAllValueWithVersion) { + { + std::map > expected_list{ + std::make_pair("test_key", std::make_pair("test_value", 1))}; + + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value", 0), 0); + EXPECT_EQ(storage->GetAllItems(), expected_list); + } + + { + std::map > expected_list{ + std::make_pair("test_key", std::make_pair("test_value_v2", 2))}; + EXPECT_EQ(storage->SetValueWithVersion("test_key", "test_value_v2", 1), 0); + EXPECT_EQ(storage->GetAllItems(), expected_list); + } + + { + std::map > expected_list{ + std::make_pair("test_key_v1", std::make_pair("test_value1", 1)), + std::make_pair("test_key", std::make_pair("test_value_v2", 2))}; + EXPECT_EQ(storage->SetValueWithVersion("test_key_v1", "test_value1", 0), 0); + EXPECT_EQ(storage->GetAllItems(), expected_list); + } +} + +TEST_P(KVStorageTest, GetKeyRange) { + EXPECT_EQ(storage->SetValueWithVersion("1", "value1", 0), 0); + EXPECT_EQ(storage->SetValueWithVersion("2", "value2", 0), 0); + EXPECT_EQ(storage->SetValueWithVersion("3", "value3", 0), 0); + EXPECT_EQ(storage->SetValueWithVersion("4", "value4", 0), 0); + + { + std::map > expected_list{ + std::make_pair("1", std::make_pair("value1", 1)), + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3", 1)), + std::make_pair("4", std::make_pair("value4", 1))}; + EXPECT_EQ(storage->GetKeyRange("1", "4"), expected_list); + } + + EXPECT_EQ(storage->SetValueWithVersion("3", "value3_1", 1), 0); + { + std::map > expected_list{ + std::make_pair("1", std::make_pair("value1", 1)), + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3_1", 2)), + std::make_pair("4", std::make_pair("value4", 1))}; + EXPECT_EQ(storage->GetKeyRange("1", "4"), expected_list); + } + { + std::map > expected_list{ + std::make_pair("1", std::make_pair("value1", 1)), + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3_1", 2)), + }; + EXPECT_EQ(storage->GetKeyRange("1", "3"), expected_list); + } + { + std::map > expected_list{ + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3_1", 2)), + std::make_pair("4", std::make_pair("value4", 1))}; + EXPECT_EQ(storage->GetKeyRange("2", "4"), expected_list); + } + { + std::map > expected_list{ + std::make_pair("1", std::make_pair("value1", 1)), + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3_1", 2)), + std::make_pair("4", std::make_pair("value4", 1))}; + EXPECT_EQ(storage->GetKeyRange("0", "5"), expected_list); + } + { + std::map > expected_list{ + std::make_pair("2", std::make_pair("value2", 1)), + std::make_pair("3", std::make_pair("value3_1", 2)), + }; + EXPECT_EQ(storage->GetKeyRange("2", "3"), expected_list); + } +} + +TEST_P(KVStorageTest, GetHistory) { + { + std::vector > expected_list{}; + EXPECT_EQ(storage->GetHistory("1", 1, 5), expected_list); + } + { + std::vector > expected_list{ + std::make_pair("value3", 3), std::make_pair("value2", 2), + std::make_pair("value1", 1)}; + + EXPECT_EQ(storage->SetValueWithVersion("1", "value1", 0), 0); + EXPECT_EQ(storage->SetValueWithVersion("1", "value2", 1), 0); + EXPECT_EQ(storage->SetValueWithVersion("1", "value3", 2), 0); + + EXPECT_EQ(storage->GetHistory("1", 1, 5), expected_list); + } + + { + std::vector > expected_list{ + std::make_pair("value5", 5), std::make_pair("value4", 4), + std::make_pair("value3", 3), std::make_pair("value2", 2), + std::make_pair("value1", 1)}; + + EXPECT_EQ(storage->SetValueWithVersion("1", "value4", 3), 0); + EXPECT_EQ(storage->SetValueWithVersion("1", "value5", 4), 0); + EXPECT_EQ(storage->SetValueWithVersion("1", "value6", 5), 0); + EXPECT_EQ(storage->SetValueWithVersion("1", "value7", 6), 0); + + EXPECT_EQ(storage->GetHistory("1", 1, 5), expected_list); + } + + { + std::vector > expected_list{ + std::make_pair("value7", 7), std::make_pair("value6", 6)}; + + EXPECT_EQ(storage->GetTopHistory("1", 2), expected_list); + } +} + +INSTANTIATE_TEST_CASE_P(KVStorageTest, KVStorageTest, + ::testing::Values(MEM, LEVELDB, ROCKSDB)); + +} // namespace +} // namespace storage +} // namespace resdb diff --git a/chain/storage/leveldb.cpp b/chain/storage/leveldb.cpp new file mode 100644 index 000000000..a74bf1643 --- /dev/null +++ b/chain/storage/leveldb.cpp @@ -0,0 +1,296 @@ +/* + * Copyright (c) 2019-2022 ExpoLab, UC Davis + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "chain/storage/leveldb.h" + +#include + +#include "chain/storage/proto/kv.pb.h" + +namespace resdb { +namespace storage { + +std::unique_ptr NewResLevelDB(const std::string& path, + std::optional config) { + if (config == std::nullopt) { + config = LevelDBInfo(); + } + (*config).set_path(path); + return std::make_unique(config); +} + +std::unique_ptr NewResLevelDB(std::optional config) { + return std::make_unique(config); +} + +ResLevelDB::ResLevelDB(std::optional config) { + std::string path = "/tmp/nexres-leveldb"; + if (config.has_value()) { + write_buffer_size_ = (*config).write_buffer_size_mb() << 20; + write_batch_size_ = (*config).write_batch_size(); + if (!(*config).path().empty()) { + LOG(ERROR) << "Custom path for ResLevelDB provided in config: " + << (*config).path(); + path = (*config).path(); + } + } + CreateDB(path); +} + +void ResLevelDB::CreateDB(const std::string& path) { + LOG(ERROR) << "ResLevelDB Create DB: path:" << path + << " write buffer size:" << write_buffer_size_ + << " batch size:" << write_batch_size_; + leveldb::Options options; + options.create_if_missing = true; + options.write_buffer_size = write_buffer_size_; + + leveldb::DB* db = nullptr; + leveldb::Status status = leveldb::DB::Open(options, path, &db); + if (status.ok()) { + db_ = std::unique_ptr(db); + } + assert(status.ok()); + LOG(ERROR) << "Successfully opened LevelDB"; +} + +ResLevelDB::~ResLevelDB() { + if (db_) { + db_.reset(); + } +} + +int ResLevelDB::SetValue(const std::string& key, const std::string& value) { + batch_.Put(key, value); + + if (batch_.ApproximateSize() >= write_batch_size_) { + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch_); + if (status.ok()) { + batch_.Clear(); + return 0; + } else { + LOG(ERROR) << "flush buffer fail:" << status.ToString(); + return -1; + } + } + return 0; +} + +std::string ResLevelDB::GetValue(const std::string& key) { + std::string value = ""; + leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); + if (status.ok()) { + return value; + } else { + return ""; + } +} + +std::string ResLevelDB::GetAllValues(void) { + std::string values = "["; + leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); + bool first_iteration = true; + for (it->SeekToFirst(); it->Valid(); it->Next()) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(it->value().ToString()); + } + values.append("]"); + + delete it; + return values; +} + +std::string ResLevelDB::GetRange(const std::string& min_key, + const std::string& max_key) { + std::string values = "["; + leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); + bool first_iteration = true; + for (it->Seek(min_key); it->Valid() && it->key().ToString() <= max_key; + it->Next()) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(it->value().ToString()); + } + values.append("]"); + + delete it; + return values; +} + +bool ResLevelDB::Flush() { + leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch_); + if (status.ok()) { + batch_.Clear(); + return true; + } + LOG(ERROR) << "flush buffer fail:" << status.ToString(); + return false; +} + +int ResLevelDB::SetValueWithVersion(const std::string& key, + const std::string& value, int version) { + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return -2; + } + + int last_v = 0; + if (history.value_size() > 0) { + last_v = history.value(history.value_size() - 1).version(); + } + + if (last_v != version) { + LOG(ERROR) << "version does not match:" << version + << " old version:" << last_v; + return -2; + } + + Value* new_value = history.add_value(); + new_value->set_value(value); + new_value->set_version(version + 1); + + history.SerializeToString(&value_str); + return SetValue(key, value_str); +} + +std::pair ResLevelDB::GetValueWithVersion( + const std::string& key, int version) { + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return std::make_pair("", 0); + } + if (history.value_size() == 0) { + return std::make_pair("", 0); + } + if (version > 0) { + for (int i = history.value_size() - 1; i >= 0; --i) { + if (history.value(i).version() == version) { + return std::make_pair(history.value(i).value(), + history.value(i).version()); + } + if (history.value(i).version() < version) { + break; + } + } + } + int last_idx = history.value_size() - 1; + return std::make_pair(history.value(last_idx).value(), + history.value(last_idx).version()); +} + +// Return a map of > +std::map> ResLevelDB::GetAllItems() { + std::map> resp; + + leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + ValueHistory history; + if (!history.ParseFromString(it->value().ToString()) || + history.value_size() == 0) { + LOG(ERROR) << "old_value parse fail"; + continue; + } + const Value& value = history.value(history.value_size() - 1); + resp.insert(std::make_pair(it->key().ToString(), + std::make_pair(value.value(), value.version()))); + } + delete it; + + return resp; +} + +std::map> ResLevelDB::GetKeyRange( + const std::string& min_key, const std::string& max_key) { + std::map> resp; + + leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); + for (it->Seek(min_key); it->Valid() && it->key().ToString() <= max_key; + it->Next()) { + ValueHistory history; + if (!history.ParseFromString(it->value().ToString()) || + history.value_size() == 0) { + LOG(ERROR) << "old_value parse fail"; + continue; + } + const Value& value = history.value(history.value_size() - 1); + resp.insert(std::make_pair(it->key().ToString(), + std::make_pair(value.value(), value.version()))); + } + delete it; + + return resp; +} + +// Return a list of +std::vector> ResLevelDB::GetHistory( + const std::string& key, int min_version, int max_version) { + std::vector> resp; + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return resp; + } + + for (int i = history.value_size() - 1; i >= 0; --i) { + if (history.value(i).version() < min_version) { + break; + } + if (history.value(i).version() <= max_version) { + resp.push_back( + std::make_pair(history.value(i).value(), history.value(i).version())); + } + } + + return resp; +} + +// Return a list of +std::vector> ResLevelDB::GetTopHistory( + const std::string& key, int top_number) { + std::vector> resp; + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return resp; + } + + for (int i = history.value_size() - 1; + i >= 0 && resp.size() < static_cast(top_number); --i) { + resp.push_back( + std::make_pair(history.value(i).value(), history.value(i).version())); + } + + return resp; +} + +} // namespace storage +} // namespace resdb diff --git a/chain/storage/res_leveldb.h b/chain/storage/leveldb.h similarity index 60% rename from chain/storage/res_leveldb.h rename to chain/storage/leveldb.h index f2c526319..a7859c362 100644 --- a/chain/storage/res_leveldb.h +++ b/chain/storage/leveldb.h @@ -29,19 +29,22 @@ #include #include +#include "chain/storage/proto/leveldb_config.pb.h" #include "chain/storage/storage.h" #include "leveldb/db.h" #include "leveldb/write_batch.h" -#include "platform/proto/replica_info.pb.h" namespace resdb { +namespace storage { -std::unique_ptr NewResLevelDB(const char* cert_file, - resdb::ResConfigData config_data); +std::unique_ptr NewResLevelDB( + const std::string& path, std::optional config = std::nullopt); +std::unique_ptr NewResLevelDB( + std::optional config = std::nullopt); class ResLevelDB : public Storage { public: - ResLevelDB(const char* cert_file, std::optional config_data); + ResLevelDB(std::optional config_data = std::nullopt); virtual ~ResLevelDB(); int SetValue(const std::string& key, const std::string& value) override; @@ -50,6 +53,24 @@ class ResLevelDB : public Storage { std::string GetRange(const std::string& min_key, const std::string& max_key) override; + int SetValueWithVersion(const std::string& key, const std::string& value, + int version) override; + std::pair GetValueWithVersion(const std::string& key, + int version) override; + + // Return a map of > + std::map> GetAllItems() override; + std::map> GetKeyRange( + const std::string& min_key, const std::string& max_key) override; + + // Return a list of + std::vector> GetHistory(const std::string& key, + int min_version, + int max_version) override; + + std::vector> GetTopHistory( + const std::string& key, int top_number) override; + bool Flush() override; private: @@ -62,4 +83,5 @@ class ResLevelDB : public Storage { unsigned int write_batch_size_ = 1; }; +} // namespace storage } // namespace resdb diff --git a/chain/storage/memory_db.cpp b/chain/storage/memory_db.cpp new file mode 100644 index 000000000..27b97d472 --- /dev/null +++ b/chain/storage/memory_db.cpp @@ -0,0 +1,176 @@ +/* + * Copyright (c) 2019-2022 ExpoLab, UC Davis + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "chain/storage/memory_db.h" + +#include + +namespace resdb { +namespace storage { + +std::unique_ptr NewMemoryDB() { return std::make_unique(); } + +MemoryDB::MemoryDB() {} + +int MemoryDB::SetValue(const std::string& key, const std::string& value) { + kv_map_[key] = value; + return 0; +} + +std::string MemoryDB::GetAllValues(void) { + std::string values = "["; + bool first_iteration = true; + for (auto kv : kv_map_) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(kv.second); + } + values.append("]"); + return values; +} + +std::string MemoryDB::GetRange(const std::string& min_key, + const std::string& max_key) { + std::string values = "["; + bool first_iteration = true; + for (auto kv : kv_map_) { + if (kv.first >= min_key && kv.first <= max_key) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(kv.second); + } + } + values.append("]"); + return values; +} + +std::string MemoryDB::GetValue(const std::string& key) { + auto search = kv_map_.find(key); + if (search != kv_map_.end()) + return search->second; + else { + return ""; + } +} + +int MemoryDB::SetValueWithVersion(const std::string& key, + const std::string& value, int version) { + auto it = kv_map_with_v_.find(key); + if ((it == kv_map_with_v_.end() && version != 0) || + (it != kv_map_with_v_.end() && it->second.back().second != version)) { + LOG(ERROR) << " value version not match. key:" << key << " db version:" + << (it == kv_map_with_v_.end() ? 0 : it->second.back().second) + << " user version:" << version; + return -2; + } + kv_map_with_v_[key].push_back(std::make_pair(value, version + 1)); + return 0; +} + +std::pair MemoryDB::GetValueWithVersion( + const std::string& key, int version) { + auto search_it = kv_map_with_v_.find(key); + if (search_it != kv_map_with_v_.end() && search_it->second.size()) { + auto it = search_it->second.end(); + do { + --it; + if (it->second == version) { + return *it; + } + if (it->second < version) { + break; + } + } while (it != search_it->second.begin()); + it = --search_it->second.end(); + LOG(ERROR) << " key:" << key << " no version:" << version + << " return max:" << it->second; + return *it; + } + return std::make_pair("", 0); +} + +std::map> MemoryDB::GetAllItems() { + std::map> resp; + + for (const auto& it : kv_map_with_v_) { + resp.insert(std::make_pair(it.first, it.second.back())); + } + return resp; +} + +std::map> MemoryDB::GetKeyRange( + const std::string& min_key, const std::string& max_key) { + LOG(ERROR) << "min key:" << min_key << " max key:" << max_key; + std::map> resp; + for (const auto& it : kv_map_with_v_) { + if (it.first >= min_key && it.first <= max_key) { + resp.insert(std::make_pair(it.first, it.second.back())); + } + } + return resp; +} + +std::vector> MemoryDB::GetHistory( + const std::string& key, int min_version, int max_version) { + std::vector> resp; + auto search_it = kv_map_with_v_.find(key); + if (search_it == kv_map_with_v_.end()) { + return resp; + } + + auto it = search_it->second.end(); + do { + --it; + if (it->second < min_version) { + break; + } + if (it->second <= max_version) { + resp.push_back(*it); + } + } while (it != search_it->second.begin()); + return resp; +} + +std::vector> MemoryDB::GetTopHistory( + const std::string& key, int top_number) { + std::vector> resp; + auto search_it = kv_map_with_v_.find(key); + if (search_it == kv_map_with_v_.end()) { + return resp; + } + + auto it = search_it->second.end(); + do { + --it; + resp.push_back(*it); + if (resp.size() >= static_cast(top_number)) { + break; + } + } while (it != search_it->second.begin()); + return resp; +} + +} // namespace storage +} // namespace resdb diff --git a/chain/storage/memory_db.h b/chain/storage/memory_db.h new file mode 100644 index 000000000..ad69ba472 --- /dev/null +++ b/chain/storage/memory_db.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2019-2022 ExpoLab, UC Davis + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#pragma once + +#include +#include +#include +#include + +#include "chain/storage/storage.h" + +namespace resdb { +namespace storage { + +// Key Value Storage supporting two types of interfaces: +// Non-version: +// It provides set and get function to set a value to a specific key +// Values will be set directly. +// Version: +// It provides set and get function to set a value to a specific key +// with a version. +// The version inside setting function is to support OCC verification. +// The version value should be obtained before setting +// and returned back when setting a new value. If the version is not +// the same as the old one, return failure. +// If the value of a specific version does not exist or providing +// version 0 as parameter when accessing get +// it returns the current value along its version. +// +// Note: Only one type of interface are allowed to be used. +// + +std::unique_ptr NewMemoryDB(); + +class MemoryDB : public Storage { + public: + MemoryDB(); + + int SetValue(const std::string& key, const std::string& value); + std::string GetValue(const std::string& key); + + std::string GetAllValues() override; + std::string GetRange(const std::string& min_key, + const std::string& max_key) override; + + int SetValueWithVersion(const std::string& key, const std::string& value, + int version) override; + std::pair GetValueWithVersion(const std::string& key, + int version) override; + + // Return a map of > + std::map> GetAllItems() override; + std::map> GetKeyRange( + const std::string& min_key, const std::string& max_key) override; + + // Return a list of + std::vector> GetHistory(const std::string& key, + int min_version, + int max_version) override; + + std::vector> GetTopHistory(const std::string& key, + int number) override; + + private: + std::unordered_map kv_map_; + std::unordered_map>> + kv_map_with_v_; +}; + +} // namespace storage +} // namespace resdb diff --git a/chain/storage/mock_storage.h b/chain/storage/mock_storage.h index 7aac187bf..5ed13ac92 100644 --- a/chain/storage/mock_storage.h +++ b/chain/storage/mock_storage.h @@ -38,6 +38,24 @@ class MockStorage : public Storage { MOCK_METHOD(std::string, GetAllValues, (), (override)); MOCK_METHOD(std::string, GetRange, (const std::string&, const std::string&), (override)); + + MOCK_METHOD(int, SetValueWithVersion, + (const std::string& key, const std::string& value, int version), + (override)); + + using ValueType = std::pair; + using ItemsType = std::map; + using ValuesType = std::vector; + + MOCK_METHOD(ValueType, GetValueWithVersion, + (const std::string& key, int version), (override)); + MOCK_METHOD(ItemsType, GetAllItems, (), (override)); + MOCK_METHOD(ItemsType, GetKeyRange, (const std::string&, const std::string&), + (override)); + MOCK_METHOD(ValuesType, GetHistory, (const std::string&, int, int), + (override)); + MOCK_METHOD(ValuesType, GetTopHistory, (const std::string&, int), (override)); + MOCK_METHOD(bool, Flush, (), (override)); }; diff --git a/chain/storage/proto/BUILD b/chain/storage/proto/BUILD new file mode 100644 index 000000000..1dea24a1a --- /dev/null +++ b/chain/storage/proto/BUILD @@ -0,0 +1,36 @@ +package(default_visibility = ["//visibility:public"]) + +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") + +proto_library( + name = "kv_proto", + srcs = ["kv.proto"], + visibility = ["//chain/storage:__subpackages__"], +) + +cc_proto_library( + name = "kv_cc_proto", + visibility = ["//chain/storage:__subpackages__"], + deps = [":kv_proto"], +) + +proto_library( + name = "leveldb_config_proto", + srcs = ["leveldb_config.proto"], +) + +cc_proto_library( + name = "leveldb_config_cc_proto", + deps = [":leveldb_config_proto"], +) + +proto_library( + name = "rocksdb_config_proto", + srcs = ["rocksdb_config.proto"], +) + +cc_proto_library( + name = "rocksdb_config_cc_proto", + deps = [":rocksdb_config_proto"], +) diff --git a/chain/storage/proto/kv.proto b/chain/storage/proto/kv.proto new file mode 100644 index 000000000..bb2e89451 --- /dev/null +++ b/chain/storage/proto/kv.proto @@ -0,0 +1,13 @@ +syntax = "proto3"; + +package resdb.storage; + +message Value { + bytes value = 1; + int32 version = 2; +} + +message ValueHistory { + repeated Value value = 1; +} + diff --git a/chain/storage/proto/leveldb_config.proto b/chain/storage/proto/leveldb_config.proto new file mode 100644 index 000000000..572b4a08a --- /dev/null +++ b/chain/storage/proto/leveldb_config.proto @@ -0,0 +1,9 @@ +syntax = "proto3"; + +package resdb.storage; + +message LevelDBInfo { + uint32 write_buffer_size_mb = 2; + uint32 write_batch_size = 3; + string path = 4; +} diff --git a/chain/storage/proto/rocksdb_config.proto b/chain/storage/proto/rocksdb_config.proto new file mode 100644 index 000000000..a1a453961 --- /dev/null +++ b/chain/storage/proto/rocksdb_config.proto @@ -0,0 +1,11 @@ +syntax = "proto3"; + +package resdb.storage; + +message RocksDBInfo { + uint32 num_threads = 2; + uint32 write_buffer_size_mb = 3; + uint32 write_batch_size = 4; + string path = 5; +} + diff --git a/chain/storage/res_leveldb.cpp b/chain/storage/res_leveldb.cpp deleted file mode 100644 index 0cd48ccae..000000000 --- a/chain/storage/res_leveldb.cpp +++ /dev/null @@ -1,169 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "chain/storage/res_leveldb.h" - -#include - -namespace resdb { - -std::unique_ptr NewResLevelDB(const char* cert_file, - resdb::ResConfigData config_data) { - return std::make_unique(cert_file, config_data); -} - -ResLevelDB::ResLevelDB(const char* cert_file, - std::optional config_data) { - std::string directory_id = ""; - - std::string path = "/tmp/nexres-leveldb"; - if (cert_file == NULL) { - LOG(ERROR) << "No cert file provided"; - } else { - LOG(ERROR) << "Cert file: " << cert_file; - std::string str(cert_file); - - for (int i = 0; i < (int)str.size(); i++) { - if (str[i] >= '0' && str[i] <= '9') { - directory_id += std::string(1, cert_file[i]); - } - } - - if (directory_id == "") { - directory_id = "0"; - } - } - - if (config_data.has_value()) { - LevelDBInfo config = (*config_data).leveldb_info(); - write_buffer_size_ = config.write_buffer_size_mb() << 20; - write_batch_size_ = config.write_batch_size(); - if (config.path() != "") { - LOG(ERROR) << "Custom path for ResLevelDB provided in config: " - << config.path(); - path = config.path(); - } - if (config.generate_unique_pathnames()) { - LOG(ERROR) << "Adding number to generate unique pathname: " - << directory_id; - path += directory_id; - } - } - CreateDB(path); -} - -void ResLevelDB::CreateDB(const std::string& path) { - LOG(ERROR) << "ResLevelDB Create DB: path:" << path - << " write buffer size:" << write_buffer_size_ - << " batch size:" << write_batch_size_; - leveldb::Options options; - options.create_if_missing = true; - options.write_buffer_size = write_buffer_size_; - - leveldb::DB* db = nullptr; - leveldb::Status status = leveldb::DB::Open(options, path, &db); - if (status.ok()) { - db_ = std::unique_ptr(db); - } - assert(status.ok()); - LOG(ERROR) << "Successfully opened LevelDB"; -} - -ResLevelDB::~ResLevelDB() { - if (db_) { - db_.reset(); - } -} - -int ResLevelDB::SetValue(const std::string& key, const std::string& value) { - batch_.Put(key, value); - - if (batch_.ApproximateSize() >= write_batch_size_) { - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch_); - if (status.ok()) { - batch_.Clear(); - return 0; - } else { - LOG(ERROR) << "flush buffer fail:" << status.ToString(); - return -1; - } - } - return 0; -} - -std::string ResLevelDB::GetValue(const std::string& key) { - std::string value = ""; - leveldb::Status status = db_->Get(leveldb::ReadOptions(), key, &value); - if (status.ok()) { - return value; - } else { - LOG(ERROR) << "get value fail:" << status.ToString(); - return ""; - } -} - -std::string ResLevelDB::GetAllValues(void) { - std::string values = "["; - leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); - bool first_iteration = true; - for (it->SeekToFirst(); it->Valid(); it->Next()) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(it->value().ToString()); - } - values.append("]"); - - delete it; - return values; -} - -std::string ResLevelDB::GetRange(const std::string& min_key, - const std::string& max_key) { - std::string values = "["; - leveldb::Iterator* it = db_->NewIterator(leveldb::ReadOptions()); - bool first_iteration = true; - for (it->Seek(min_key); it->Valid() && it->key().ToString() <= max_key; - it->Next()) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(it->value().ToString()); - } - values.append("]"); - - delete it; - return values; -} - -bool ResLevelDB::Flush() { - leveldb::Status status = db_->Write(leveldb::WriteOptions(), &batch_); - if (status.ok()) { - batch_.Clear(); - return true; - } - LOG(ERROR) << "flush buffer fail:" << status.ToString(); - return false; -} - -} // namespace resdb diff --git a/chain/storage/res_leveldb_test.cpp b/chain/storage/res_leveldb_test.cpp deleted file mode 100644 index 17deddffd..000000000 --- a/chain/storage/res_leveldb_test.cpp +++ /dev/null @@ -1,111 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "chain/storage/res_leveldb.h" - -#include -#include -#include - -#include - -namespace resdb { - -namespace { - -using ::testing::Test; - -class ResLevelDBDurableTest : public Test { - public: - ResLevelDBDurableTest() { Reset(); } - ~ResLevelDBDurableTest() {} - int Set(const std::string& key, const std::string& value) { - resdb::ResConfigData config_data; - config_data.mutable_leveldb_info()->set_path(path_); - return NewResLevelDB(NULL, config_data)->SetValue(key, value); - } - - std::string Get(const std::string& key) { - resdb::ResConfigData config_data; - config_data.mutable_leveldb_info()->set_path(path_); - return NewResLevelDB(NULL, config_data)->GetValue(key); - } - - std::string GetAllValues() { - resdb::ResConfigData config_data; - config_data.mutable_leveldb_info()->set_path(path_); - return NewResLevelDB(NULL, config_data)->GetAllValues(); - } - - std::string GetRange(const std::string& min_key, const std::string& max_key) { - resdb::ResConfigData config_data; - config_data.mutable_leveldb_info()->set_path(path_); - return NewResLevelDB(NULL, config_data)->GetRange(min_key, max_key); - } - - void Reset() { std::filesystem::remove_all(path_.c_str()); } - - private: - std::string path_ = "/tmp/leveldb_test"; -}; - -TEST_F(ResLevelDBDurableTest, GetEmptyValue) { - EXPECT_EQ(Get("empty_key"), ""); -} - -TEST_F(ResLevelDBDurableTest, SetValue) { - EXPECT_EQ(Set("test_key", "test_value"), 0); -} - -TEST_F(ResLevelDBDurableTest, GetValue) { - EXPECT_EQ(Set("test_key", "test_value"), 0); - EXPECT_EQ(Get("test_key"), "test_value"); -} - -TEST_F(ResLevelDBDurableTest, SetNewValue) { - EXPECT_EQ(Set("test_key", "new_value"), 0); -} - -TEST_F(ResLevelDBDurableTest, GetNewValue) { EXPECT_EQ(Get("test_key"), ""); } - -TEST_F(ResLevelDBDurableTest, GetAllValues) { - EXPECT_EQ(Set("a", "a"), 0); - EXPECT_EQ(Set("b", "b"), 0); - EXPECT_EQ(Set("c", "c"), 0); - EXPECT_EQ(GetAllValues(), "[a,b,c]"); -} - -TEST_F(ResLevelDBDurableTest, GetRange) { - EXPECT_EQ(Set("key1", "value1"), 0); - EXPECT_EQ(Set("key2", "value2"), 0); - EXPECT_EQ(Set("key3", "value3"), 0); - EXPECT_EQ(GetRange("key1", "key3"), "[value1,value2,value3]"); - EXPECT_EQ(GetRange("key1", "key2"), "[value1,value2]"); - EXPECT_EQ(GetRange("key4", "key5"), "[]"); -} - -} // namespace - -} // namespace resdb diff --git a/chain/storage/res_rocksdb.cpp b/chain/storage/res_rocksdb.cpp deleted file mode 100644 index 2655aca2a..000000000 --- a/chain/storage/res_rocksdb.cpp +++ /dev/null @@ -1,171 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "chain/storage/res_rocksdb.h" - -#include -#include - -namespace resdb { - -std::unique_ptr NewResRocksDB( - const char* cert_file, std::optional config_data) { - return std::make_unique(cert_file, config_data); -} - -ResRocksDB::ResRocksDB(const char* cert_file, - std::optional config_data) { - std::string directory_id = ""; - - std::string path = "/tmp/nexres-rocksdb"; - LOG(ERROR) << "Default database path: " << path; - - if (cert_file == NULL) { - LOG(ERROR) << "No cert file provided"; - } else { - LOG(ERROR) << "Cert file: " << cert_file; - std::string str(cert_file); - - for (int i = 0; i < (int)str.size(); i++) { - if (str[i] >= '0' && str[i] <= '9') { - directory_id += std::string(1, cert_file[i]); - } - } - - if (directory_id == "") { - directory_id = "0"; - } - } - - if (config_data.has_value()) { - RocksDBInfo config = (*config_data).rocksdb_info(); - num_threads_ = config.num_threads(); - write_buffer_size_ = config.write_buffer_size_mb() << 20; - write_batch_size_ = config.write_batch_size(); - if (config.path() != "") { - LOG(ERROR) << "Custom path for RocksDB provided in config: " - << config.path(); - path = config.path(); - } - if (config.generate_unique_pathnames()) { - LOG(ERROR) << "Adding number to generate unique pathname: " - << directory_id; - path += directory_id; - } - } - LOG(ERROR) << "RocksDB Settings: " << num_threads_ << " " - << write_buffer_size_ << " " << write_batch_size_; - - rocksdb::Options options; - options.create_if_missing = true; - if (num_threads_ > 1) options.IncreaseParallelism(num_threads_); - options.OptimizeLevelStyleCompaction(); - options.write_buffer_size = write_buffer_size_; - - rocksdb::DB* db = nullptr; - rocksdb::Status status = rocksdb::DB::Open(options, path, &db); - if (status.ok()) { - db_ = std::unique_ptr(db); - LOG(ERROR) << "Successfully opened RocksDB in path: " << path; - } else { - LOG(ERROR) << "RocksDB status fail"; - } - assert(status.ok()); -} - -ResRocksDB::~ResRocksDB() { - if (db_) { - db_.reset(); - } -} - -int ResRocksDB::SetValue(const std::string& key, const std::string& value) { - batch_.Put(key, value); - - if (batch_.Count() >= write_batch_size_) { - rocksdb::Status status = db_->Write(rocksdb::WriteOptions(), &batch_); - if (status.ok()) { - batch_.Clear(); - } else { - LOG(ERROR) << "write value fail:" << status.ToString(); - return -1; - } - } - return 0; -} - -std::string ResRocksDB::GetValue(const std::string& key) { - std::string value = ""; - rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), key, &value); - if (status.ok()) { - return value; - } else { - return ""; - } -} - -std::string ResRocksDB::GetAllValues(void) { - std::string values = "["; - rocksdb::Iterator* itr = db_->NewIterator(rocksdb::ReadOptions()); - bool first_iteration = true; - for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(itr->value().ToString()); - } - values.append("]"); - - delete itr; - return values; -} - -std::string ResRocksDB::GetRange(const std::string& min_key, - const std::string& max_key) { - std::string values = "["; - rocksdb::Iterator* itr = db_->NewIterator(rocksdb::ReadOptions()); - bool first_iteration = true; - for (itr->Seek(min_key); itr->Valid() && itr->key().ToString() <= max_key; - itr->Next()) { - if (!first_iteration) values.append(","); - first_iteration = false; - values.append(itr->value().ToString()); - } - values.append("]"); - - delete itr; - return values; -} - -bool ResRocksDB::Flush() { - rocksdb::Status status = db_->Write(rocksdb::WriteOptions(), &batch_); - if (status.ok()) { - batch_.Clear(); - return true; - } - LOG(ERROR) << "write value fail:" << status.ToString(); - return false; -} - -} // namespace resdb diff --git a/chain/storage/res_rocksdb_test.cpp b/chain/storage/res_rocksdb_test.cpp deleted file mode 100644 index 5c17f8b7f..000000000 --- a/chain/storage/res_rocksdb_test.cpp +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "storage/res_rocksdb.h" - -#include -#include - -#include - -namespace resdb { -namespace { - -using ::testing::Test; - -class RocksDBDurableTest : public Test { - public: - RocksDBDurableTest() { Reset(); } - - int Set(const std::string& key, const std::string& value) { - ResConfigData config_data; - config_data.mutable_rocksdb_info()->set_path(path_); - return NewResRocksDB(NULL, config_data)->SetValue(key, value); - } - - std::string Get(const std::string& key) { - ResConfigData config_data; - config_data.mutable_rocksdb_info()->set_path(path_); - return NewResRocksDB(NULL, config_data)->GetValue(key); - } - - std::string GetAllValues() { - ResConfigData config_data; - config_data.mutable_rocksdb_info()->set_path(path_); - return NewResRocksDB(NULL, config_data)->GetAllValues(); - } - - std::string GetRange(const std::string& min_key, const std::string& max_key) { - ResConfigData config_data; - config_data.mutable_rocksdb_info()->set_path(path_); - return NewResRocksDB(NULL, config_data)->GetRange(min_key, max_key); - } - - void Reset() { std::filesystem::remove_all(path_.c_str()); } - - private: - std::string path_ = "/tmp/rocksdb_test"; -}; - -TEST_F(RocksDBDurableTest, GetEmptyValue) { EXPECT_EQ(Get("empty_key"), ""); } - -TEST_F(RocksDBDurableTest, SetValue) { - EXPECT_EQ(Set("test_key", "test_value"), 0); -} - -TEST_F(RocksDBDurableTest, GetValue) { - EXPECT_EQ(Set("test_key", "test_value"), 0); - EXPECT_EQ(Get("test_key"), "test_value"); -} - -TEST_F(RocksDBDurableTest, SetNewValue) { - EXPECT_EQ(Set("test_key", "new_value"), 0); -} - -TEST_F(RocksDBDurableTest, GetNewValue) { - EXPECT_EQ(Set("test_key", "new_value1"), 0); - EXPECT_EQ(Get("test_key"), "new_value1"); -} - -TEST_F(RocksDBDurableTest, GetAllValues) { - EXPECT_EQ(Set("a", "a"), 0); - EXPECT_EQ(Set("b", "b"), 0); - EXPECT_EQ(Set("c", "c"), 0); - EXPECT_EQ(GetAllValues(), "[a,b,c]"); -} - -TEST_F(RocksDBDurableTest, GetRange) { - EXPECT_EQ(Set("key1", "value1"), 0); - EXPECT_EQ(Set("key2", "value2"), 0); - EXPECT_EQ(Set("key3", "value3"), 0); - EXPECT_EQ(GetRange("key1", "key3"), "[value1,value2,value3]"); - EXPECT_EQ(GetRange("key1", "key2"), "[value1,value2]"); - EXPECT_EQ(GetRange("key4", "key5"), "[]"); -} - -} // namespace -} // namespace resdb diff --git a/chain/storage/rocksdb.cpp b/chain/storage/rocksdb.cpp new file mode 100644 index 000000000..37d6028e5 --- /dev/null +++ b/chain/storage/rocksdb.cpp @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2019-2022 ExpoLab, UC Davis + * + * Permission is hereby granted, free of charge, to any person + * obtaining a copy of this software and associated documentation + * files (the "Software"), to deal in the Software without + * restriction, including without limitation the rights to use, + * copy, modify, merge, publish, distribute, sublicense, and/or + * sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be + * included in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, + * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES + * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND + * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT + * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, + * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + * + */ + +#include "chain/storage/rocksdb.h" + +#include + +#include "chain/storage/proto/kv.pb.h" + +namespace resdb { +namespace storage { + +std::unique_ptr NewResRocksDB(const std::string& path, + std::optional config) { + if (config == std::nullopt) { + config = RocksDBInfo(); + } + (*config).set_path(path); + return std::make_unique(config); +} + +std::unique_ptr NewResRocksDB(std::optional config) { + return std::make_unique(config); +} + +ResRocksDB::ResRocksDB(std::optional config) { + std::string path = "/tmp/nexres-rocksdb"; + if (config.has_value()) { + num_threads_ = (*config).num_threads(); + write_buffer_size_ = (*config).write_buffer_size_mb() << 20; + write_batch_size_ = (*config).write_batch_size(); + if ((*config).path() != "") { + LOG(ERROR) << "Custom path for RocksDB provided in config: " + << (*config).path(); + path = (*config).path(); + } + } + CreateDB(path); +} + +void ResRocksDB::CreateDB(const std::string& path) { + rocksdb::Options options; + options.create_if_missing = true; + if (num_threads_ > 1) options.IncreaseParallelism(num_threads_); + options.OptimizeLevelStyleCompaction(); + options.write_buffer_size = write_buffer_size_; + + rocksdb::DB* db = nullptr; + rocksdb::Status status = rocksdb::DB::Open(options, path, &db); + if (status.ok()) { + db_ = std::unique_ptr(db); + LOG(ERROR) << "Successfully opened RocksDB in path: " << path; + } else { + LOG(ERROR) << "RocksDB status fail"; + } + assert(status.ok()); + LOG(ERROR) << "Successfully opened RocksDB"; +} + +ResRocksDB::~ResRocksDB() { + if (db_) { + db_.reset(); + } +} + +int ResRocksDB::SetValue(const std::string& key, const std::string& value) { + batch_.Put(key, value); + + if (batch_.Count() >= write_batch_size_) { + rocksdb::Status status = db_->Write(rocksdb::WriteOptions(), &batch_); + if (status.ok()) { + batch_.Clear(); + } else { + LOG(ERROR) << "write value fail:" << status.ToString(); + return -1; + } + } + return 0; +} + +std::string ResRocksDB::GetValue(const std::string& key) { + std::string value = ""; + rocksdb::Status status = db_->Get(rocksdb::ReadOptions(), key, &value); + if (status.ok()) { + return value; + } else { + return ""; + } +} + +std::string ResRocksDB::GetAllValues(void) { + std::string values = "["; + rocksdb::Iterator* itr = db_->NewIterator(rocksdb::ReadOptions()); + bool first_iteration = true; + for (itr->SeekToFirst(); itr->Valid(); itr->Next()) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(itr->value().ToString()); + } + values.append("]"); + + delete itr; + return values; +} + +std::string ResRocksDB::GetRange(const std::string& min_key, + const std::string& max_key) { + std::string values = "["; + rocksdb::Iterator* itr = db_->NewIterator(rocksdb::ReadOptions()); + bool first_iteration = true; + for (itr->Seek(min_key); itr->Valid() && itr->key().ToString() <= max_key; + itr->Next()) { + if (!first_iteration) values.append(","); + first_iteration = false; + values.append(itr->value().ToString()); + } + values.append("]"); + + delete itr; + return values; +} + +bool ResRocksDB::Flush() { + rocksdb::Status status = db_->Write(rocksdb::WriteOptions(), &batch_); + if (status.ok()) { + batch_.Clear(); + return true; + } + LOG(ERROR) << "write value fail:" << status.ToString(); + return false; +} +int ResRocksDB::SetValueWithVersion(const std::string& key, + const std::string& value, int version) { + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return -2; + } + + int last_v = 0; + if (history.value_size() > 0) { + last_v = history.value(history.value_size() - 1).version(); + } + + if (last_v != version) { + LOG(ERROR) << "version does not match:" << version + << " old version:" << last_v; + return -2; + } + + Value* new_value = history.add_value(); + new_value->set_value(value); + new_value->set_version(version + 1); + + history.SerializeToString(&value_str); + return SetValue(key, value_str); +} + +std::pair ResRocksDB::GetValueWithVersion( + const std::string& key, int version) { + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return std::make_pair("", 0); + } + if (history.value_size() == 0) { + return std::make_pair("", 0); + } + if (version > 0) { + for (int i = history.value_size() - 1; i >= 0; --i) { + if (history.value(i).version() == version) { + return std::make_pair(history.value(i).value(), + history.value(i).version()); + } + if (history.value(i).version() < version) { + break; + } + } + } + int last_idx = history.value_size() - 1; + return std::make_pair(history.value(last_idx).value(), + history.value(last_idx).version()); +} + +// Return a map of > +std::map> ResRocksDB::GetAllItems() { + std::map> resp; + + rocksdb::Iterator* it = db_->NewIterator(rocksdb::ReadOptions()); + for (it->SeekToFirst(); it->Valid(); it->Next()) { + ValueHistory history; + if (!history.ParseFromString(it->value().ToString()) || + history.value_size() == 0) { + LOG(ERROR) << "old_value parse fail"; + continue; + } + const Value& value = history.value(history.value_size() - 1); + resp.insert(std::make_pair(it->key().ToString(), + std::make_pair(value.value(), value.version()))); + } + delete it; + + return resp; +} + +std::map> ResRocksDB::GetKeyRange( + const std::string& min_key, const std::string& max_key) { + std::map> resp; + + rocksdb::Iterator* it = db_->NewIterator(rocksdb::ReadOptions()); + for (it->Seek(min_key); it->Valid() && it->key().ToString() <= max_key; + it->Next()) { + ValueHistory history; + if (!history.ParseFromString(it->value().ToString()) || + history.value_size() == 0) { + LOG(ERROR) << "old_value parse fail"; + continue; + } + const Value& value = history.value(history.value_size() - 1); + resp.insert(std::make_pair(it->key().ToString(), + std::make_pair(value.value(), value.version()))); + } + delete it; + + return resp; +} + +// Return a list of +std::vector> ResRocksDB::GetHistory( + const std::string& key, int min_version, int max_version) { + std::vector> resp; + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return resp; + } + + for (int i = history.value_size() - 1; i >= 0; --i) { + if (history.value(i).version() < min_version) { + break; + } + if (history.value(i).version() <= max_version) { + resp.push_back( + std::make_pair(history.value(i).value(), history.value(i).version())); + } + } + + return resp; +} + +// Return a list of +std::vector> ResRocksDB::GetTopHistory( + const std::string& key, int top_number) { + std::vector> resp; + std::string value_str = GetValue(key); + ValueHistory history; + if (!history.ParseFromString(value_str)) { + LOG(ERROR) << "old_value parse fail"; + return resp; + } + + for (int i = history.value_size() - 1; + i >= 0 && resp.size() < static_cast(top_number); --i) { + resp.push_back( + std::make_pair(history.value(i).value(), history.value(i).version())); + } + + return resp; +} + +} // namespace storage +} // namespace resdb diff --git a/chain/storage/res_rocksdb.h b/chain/storage/rocksdb.h similarity index 57% rename from chain/storage/res_rocksdb.h rename to chain/storage/rocksdb.h index 706957c2f..cafc1d7c7 100644 --- a/chain/storage/res_rocksdb.h +++ b/chain/storage/rocksdb.h @@ -25,22 +25,26 @@ #pragma once +#include #include #include +#include "chain/storage/proto/rocksdb_config.pb.h" #include "chain/storage/storage.h" -#include "platform/proto/replica_info.pb.h" #include "rocksdb/db.h" #include "rocksdb/write_batch.h" namespace resdb { +namespace storage { std::unique_ptr NewResRocksDB( - const char* cert_file, std::optional config_data); + const std::string& path, std::optional config = std::nullopt); +std::unique_ptr NewResRocksDB( + std::optional config = std::nullopt); class ResRocksDB : public Storage { public: - ResRocksDB(const char* cert_file, std::optional config_data); + ResRocksDB(std::optional config_data = std::nullopt); virtual ~ResRocksDB(); int SetValue(const std::string& key, const std::string& value) override; std::string GetValue(const std::string& key) override; @@ -48,14 +52,35 @@ class ResRocksDB : public Storage { std::string GetRange(const std::string& min_key, const std::string& max_key) override; + int SetValueWithVersion(const std::string& key, const std::string& value, + int version) override; + std::pair GetValueWithVersion(const std::string& key, + int version) override; + + // Return a map of > + std::map> GetAllItems() override; + std::map> GetKeyRange( + const std::string& min_key, const std::string& max_key) override; + + // Return a list of + std::vector> GetHistory(const std::string& key, + int min_version, + int max_version) override; + std::vector> GetTopHistory( + const std::string& key, int top_number) override; + bool Flush() override; private: - std::unique_ptr db_ = nullptr; - rocksdb::WriteBatch batch_; + void CreateDB(const std::string& path); + + private: + std::unique_ptr<::rocksdb::DB> db_ = nullptr; + ::rocksdb::WriteBatch batch_; unsigned int num_threads_ = 1; unsigned int write_buffer_size_ = 64 << 20; unsigned int write_batch_size_ = 1; }; +} // namespace storage } // namespace resdb diff --git a/chain/storage/setting/BUILD b/chain/storage/setting/BUILD new file mode 100644 index 000000000..e7f33e35d --- /dev/null +++ b/chain/storage/setting/BUILD @@ -0,0 +1,31 @@ +package(default_visibility = ["//visibility:public"]) + +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +bool_flag( + name = "enable_leveldb", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +bool_flag( + name = "enable_rocksdb", + build_setting_default = False, + visibility = ["//visibility:public"], +) + +config_setting( + name = "enable_leveldb_setting", + values = { + "define": "enable_leveldb=True", + }, + visibility = ["//visibility:public"], +) + +config_setting( + name = "enable_rocksdb_setting", + values = { + "define": "enable_rocksdb=True", + }, + visibility = ["//visibility:public"], +) diff --git a/chain/storage/storage.h b/chain/storage/storage.h index 3ab095682..430f53790 100644 --- a/chain/storage/storage.h +++ b/chain/storage/storage.h @@ -25,7 +25,9 @@ #pragma once +#include #include +#include namespace resdb { @@ -34,22 +36,31 @@ class Storage { Storage() = default; virtual ~Storage() = default; - // Set value by key - // Return >=0 if success. virtual int SetValue(const std::string& key, const std::string& value) = 0; - - // Get value by key virtual std::string GetValue(const std::string& key) = 0; - - // Get all values in db virtual std::string GetAllValues() = 0; - - // Get values on a range of keys virtual std::string GetRange(const std::string& min_key, const std::string& max_key) = 0; - // Flush data to disk - virtual bool Flush() = 0; + virtual int SetValueWithVersion(const std::string& key, + const std::string& value, int version) = 0; + virtual std::pair GetValueWithVersion( + const std::string& key, int version) = 0; + + // Return a map of > + virtual std::map> GetAllItems() = 0; + virtual std::map> GetKeyRange( + const std::string& min_key, const std::string& max_key) = 0; + + // Return a list of from a key + // The version list is sorted by the version value in descending order + virtual std::vector> GetHistory( + const std::string& key, int min_version, int max_version) = 0; + + virtual std::vector> GetTopHistory( + const std::string& key, int number) = 0; + + virtual bool Flush() { return true; }; }; } // namespace resdb diff --git a/chain/storage/txn_memory_db.cpp b/chain/storage/txn_memory_db.cpp deleted file mode 100644 index f16445c0e..000000000 --- a/chain/storage/txn_memory_db.cpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "chain/storage/txn_memory_db.h" - -#include - -namespace resdb { - -TxnMemoryDB::TxnMemoryDB() : max_seq_(0) {} - -Request* TxnMemoryDB::Get(uint64_t seq) { - std::unique_lock lk(mutex_); - if (data_.find(seq) == data_.end()) { - return nullptr; - } - return data_[seq].get(); -} - -void TxnMemoryDB::Put(std::unique_ptr request) { - std::unique_lock lk(mutex_); - max_seq_ = request->seq(); - data_[max_seq_] = std::move(request); -} - -uint64_t TxnMemoryDB::GetMaxSeq() { return max_seq_; } - -} // namespace resdb diff --git a/chain/storage/txn_memory_db.h b/chain/storage/txn_memory_db.h deleted file mode 100644 index de69d70cb..000000000 --- a/chain/storage/txn_memory_db.h +++ /dev/null @@ -1,48 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#pragma once - -#include -#include - -#include "platform/proto/resdb.pb.h" - -namespace resdb { - -class TxnMemoryDB { - public: - TxnMemoryDB(); - Request* Get(uint64_t seq); - void Put(std::unique_ptr request); - uint64_t GetMaxSeq(); - - private: - std::mutex mutex_; - std::unordered_map > data_; - std::atomic max_seq_; -}; - -} // namespace resdb diff --git a/chain/storage/txn_memory_db_test.cpp b/chain/storage/txn_memory_db_test.cpp deleted file mode 100644 index 1ff726dcd..000000000 --- a/chain/storage/txn_memory_db_test.cpp +++ /dev/null @@ -1,71 +0,0 @@ -/* - * Copyright (c) 2019-2022 ExpoLab, UC Davis - * - * Permission is hereby granted, free of charge, to any person - * obtaining a copy of this software and associated documentation - * files (the "Software"), to deal in the Software without - * restriction, including without limitation the rights to use, - * copy, modify, merge, publish, distribute, sublicense, and/or - * sell copies of the Software, and to permit persons to whom the - * Software is furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be - * included in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, - * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES - * OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND - * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT - * HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, - * WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER - * DEALINGS IN THE SOFTWARE. - * - */ - -#include "database/txn_memory_db.h" - -#include -#include - -#include "common/test/test_macros.h" - -namespace resdb { -namespace { - -using ::resdb::testing::EqualsProto; -using ::testing::Pointee; - -TEST(TxnMemoryDBTest, GetEmptyValue) { - TxnMemoryDB db; - EXPECT_EQ(db.Get(1), nullptr); -} - -TEST(TxnMemoryDBTest, GetValue) { - Request request; - request.set_seq(1); - request.set_data("test"); - - TxnMemoryDB db; - db.Put(std::make_unique(request)); - EXPECT_THAT(db.Get(1), Pointee(EqualsProto(request))); -} - -TEST(TxnMemoryDBTest, GetSecondValue) { - Request request; - request.set_seq(1); - request.set_data("test"); - - TxnMemoryDB db; - db.Put(std::make_unique(request)); - - request.set_seq(1); - request.set_data("test_1"); - db.Put(std::make_unique(request)); - - EXPECT_THAT(db.Get(1), Pointee(EqualsProto(request))); -} - -} // namespace - -} // namespace resdb diff --git a/executor/kv/BUILD b/executor/kv/BUILD index 9117dd93b..85a5e2168 100644 --- a/executor/kv/BUILD +++ b/executor/kv/BUILD @@ -2,40 +2,12 @@ package(default_visibility = ["//visibility:public"]) load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") -bool_flag( - name = "enable_leveldb", - build_setting_default = False, - visibility = ["//visibility:public"], -) - -bool_flag( - name = "enable_rocksdb", - build_setting_default = False, - visibility = ["//visibility:public"], -) - -config_setting( - name = "enable_leveldb_setting", - values = { - "define": "enable_leveldb=True", - }, - visibility = ["//visibility:public"], -) - -config_setting( - name = "enable_rocksdb_setting", - values = { - "define": "enable_rocksdb=True", - }, - visibility = ["//visibility:public"], -) - cc_library( name = "kv_executor", srcs = ["kv_executor.cpp"], hdrs = ["kv_executor.h"], deps = [ - "//chain/state:chain_state", + "//chain/storage", "//common:comm", "//executor/common:transaction_manager", "//platform/config:resdb_config_utils", @@ -48,7 +20,7 @@ cc_test( srcs = ["kv_executor_test.cpp"], deps = [ ":kv_executor", - "//chain/storage:mock_storage", + "//chain/storage:memory_db", "//common/test:test_main", ], ) diff --git a/executor/kv/kv_executor.cpp b/executor/kv/kv_executor.cpp index e3ca4b698..e87ad75f9 100644 --- a/executor/kv/kv_executor.cpp +++ b/executor/kv/kv_executor.cpp @@ -27,12 +27,10 @@ #include -#include "proto/kv/kv.pb.h" - namespace resdb { -KVExecutor::KVExecutor(std::unique_ptr state) - : state_(std::move(state)) {} +KVExecutor::KVExecutor(std::unique_ptr storage) + : storage_(std::move(storage)) {} std::unique_ptr KVExecutor::ExecuteData( const std::string& request) { @@ -52,30 +50,104 @@ std::unique_ptr KVExecutor::ExecuteData( kv_response.set_value(GetAllValues()); } else if (kv_request.cmd() == KVRequest::GETRANGE) { kv_response.set_value(GetRange(kv_request.key(), kv_request.value())); + } else if (kv_request.cmd() == KVRequest::SET_WITH_VERSION) { + SetWithVersion(kv_request.key(), kv_request.value(), kv_request.version()); + } else if (kv_request.cmd() == KVRequest::GET_WITH_VERSION) { + GetWithVersion(kv_request.key(), kv_request.version(), + kv_response.mutable_value_info()); + } else if (kv_request.cmd() == KVRequest::GET_ALL_ITEMS) { + GetAllItems(kv_response.mutable_items()); + } else if (kv_request.cmd() == KVRequest::GET_KEY_RANGE) { + GetKeyRange(kv_request.min_key(), kv_request.max_key(), + kv_response.mutable_items()); + } else if (kv_request.cmd() == KVRequest::GET_HISTORY) { + GetHistory(kv_request.key(), kv_request.min_version(), + kv_request.max_version(), kv_response.mutable_items()); + } else if (kv_request.cmd() == KVRequest::GET_TOP) { + GetTopHistory(kv_request.key(), kv_request.top_number(), + kv_response.mutable_items()); } std::unique_ptr resp_str = std::make_unique(); if (!kv_response.SerializeToString(resp_str.get())) { return nullptr; } - return resp_str; } void KVExecutor::Set(const std::string& key, const std::string& value) { - state_->SetValue(key, value); + storage_->SetValue(key, value); } std::string KVExecutor::Get(const std::string& key) { - return state_->GetValue(key); + return storage_->GetValue(key); } -std::string KVExecutor::GetAllValues() { return state_->GetAllValues(); } +std::string KVExecutor::GetAllValues() { return storage_->GetAllValues(); } // Get values on a range of keys std::string KVExecutor::GetRange(const std::string& min_key, const std::string& max_key) { - return state_->GetRange(min_key, max_key); + return storage_->GetRange(min_key, max_key); +} + +void KVExecutor::SetWithVersion(const std::string& key, + const std::string& value, int version) { + storage_->SetValueWithVersion(key, value, version); +} + +void KVExecutor::GetWithVersion(const std::string& key, int version, + ValueInfo* info) { + std::pair ret = storage_->GetValueWithVersion(key, version); + info->set_value(ret.first); + info->set_version(ret.second); +} + +void KVExecutor::GetAllItems(Items* items) { + const std::map>& ret = + storage_->GetAllItems(); + for (auto it : ret) { + Item* item = items->add_item(); + item->set_key(it.first); + item->mutable_value_info()->set_value(it.second.first); + item->mutable_value_info()->set_version(it.second.second); + } +} + +void KVExecutor::GetKeyRange(const std::string& min_key, + const std::string& max_key, Items* items) { + const std::map>& ret = + storage_->GetKeyRange(min_key, max_key); + for (auto it : ret) { + Item* item = items->add_item(); + item->set_key(it.first); + item->mutable_value_info()->set_value(it.second.first); + item->mutable_value_info()->set_version(it.second.second); + } +} + +void KVExecutor::GetHistory(const std::string& key, int min_version, + int max_version, Items* items) { + const std::vector>& ret = + storage_->GetHistory(key, min_version, max_version); + for (auto it : ret) { + Item* item = items->add_item(); + item->set_key(key); + item->mutable_value_info()->set_value(it.first); + item->mutable_value_info()->set_version(it.second); + } +} + +void KVExecutor::GetTopHistory(const std::string& key, int top_number, + Items* items) { + const std::vector>& ret = + storage_->GetTopHistory(key, top_number); + for (auto it : ret) { + Item* item = items->add_item(); + item->set_key(key); + item->mutable_value_info()->set_value(it.first); + item->mutable_value_info()->set_version(it.second); + } } } // namespace resdb diff --git a/executor/kv/kv_executor.h b/executor/kv/kv_executor.h index c9ba010b1..d820c51f1 100644 --- a/executor/kv/kv_executor.h +++ b/executor/kv/kv_executor.h @@ -29,15 +29,15 @@ #include #include -#include "chain/state/chain_state.h" +#include "chain/storage/storage.h" #include "executor/common/transaction_manager.h" -#include "platform/config/resdb_config_utils.h" +#include "proto/kv/kv.pb.h" namespace resdb { class KVExecutor : public TransactionManager { public: - KVExecutor(std::unique_ptr state); + KVExecutor(std::unique_ptr storage); virtual ~KVExecutor() = default; std::unique_ptr ExecuteData(const std::string& request) override; @@ -48,8 +48,18 @@ class KVExecutor : public TransactionManager { std::string GetAllValues(); std::string GetRange(const std::string& min_key, const std::string& max_key); + void SetWithVersion(const std::string& key, const std::string& value, + int version); + void GetWithVersion(const std::string& key, int version, ValueInfo* info); + void GetAllItems(Items* items); + void GetKeyRange(const std::string& min_key, const std::string& max_key, + Items* items); + void GetHistory(const std::string& key, int min_key, int max_key, + Items* items); + void GetTopHistory(const std::string& key, int top_number, Items* items); + private: - std::unique_ptr state_; + std::unique_ptr storage_; }; } // namespace resdb diff --git a/executor/kv/kv_executor_test.cpp b/executor/kv/kv_executor_test.cpp index fefc432e1..e5d7ae278 100644 --- a/executor/kv/kv_executor_test.cpp +++ b/executor/kv/kv_executor_test.cpp @@ -28,13 +28,17 @@ #include #include -#include "chain/storage/mock_storage.h" +#include "chain/storage/memory_db.h" +#include "chain/storage/storage.h" +#include "common/test/test_macros.h" #include "platform/config/resdb_config_utils.h" #include "proto/kv/kv.pb.h" namespace resdb { namespace { +using ::resdb::testing::EqualsProto; +using storage::MemoryDB; using ::testing::Invoke; using ::testing::Return; using ::testing::Test; @@ -42,10 +46,9 @@ using ::testing::Test; class KVExecutorTest : public Test { public: KVExecutorTest() { - auto mock_storage = std::make_unique(); - mock_storage_ptr_ = mock_storage.get(); - impl_ = std::make_unique( - std::make_unique(std::move(mock_storage))); + auto storage = std::make_unique(); + storage_ptr_ = storage.get(); + impl_ = std::make_unique(std::move(storage)); } int Set(const std::string& key, const std::string& value) { @@ -123,8 +126,115 @@ class KVExecutorTest : public Test { return kv_response.value(); } + int Set(const std::string& key, const std::string& value, int version) { + KVRequest request; + request.set_cmd(KVRequest::SET_WITH_VERSION); + request.set_key(key); + request.set_value(value); + request.set_version(version); + + std::string str; + if (!request.SerializeToString(&str)) { + return -1; + } + + impl_->ExecuteData(str); + return 0; + } + + ValueInfo Get(const std::string& key, int version) { + KVRequest request; + request.set_cmd(KVRequest::GET_WITH_VERSION); + request.set_key(key); + request.set_version(version); + + std::string str; + if (!request.SerializeToString(&str)) { + return ValueInfo(); + } + + auto resp = impl_->ExecuteData(str); + if (resp == nullptr) { + return ValueInfo(); + } + KVResponse kv_response; + if (!kv_response.ParseFromString(*resp)) { + return ValueInfo(); + } + + return kv_response.value_info(); + } + + Items GetAllItems() { + KVRequest request; + request.set_cmd(KVRequest::GET_ALL_ITEMS); + + std::string str; + if (!request.SerializeToString(&str)) { + return Items(); + } + + auto resp = impl_->ExecuteData(str); + if (resp == nullptr) { + return Items(); + } + KVResponse kv_response; + if (!kv_response.ParseFromString(*resp)) { + return Items(); + } + + return kv_response.items(); + } + + Items GetKeyRange(const std::string& min_key, const std::string& max_key) { + KVRequest request; + request.set_cmd(KVRequest::GET_KEY_RANGE); + request.set_min_key(min_key); + request.set_max_key(max_key); + + std::string str; + if (!request.SerializeToString(&str)) { + return Items(); + } + + auto resp = impl_->ExecuteData(str); + if (resp == nullptr) { + return Items(); + } + KVResponse kv_response; + if (!kv_response.ParseFromString(*resp)) { + return Items(); + } + + return kv_response.items(); + } + + Items GetHistory(const std::string& key, int min_version, int max_version) { + KVRequest request; + request.set_cmd(KVRequest::GET_HISTORY); + request.set_key(key); + request.set_min_version(min_version); + request.set_max_version(max_version); + + std::string str; + if (!request.SerializeToString(&str)) { + return Items(); + } + + auto resp = impl_->ExecuteData(str); + if (resp == nullptr) { + return Items(); + } + KVResponse kv_response; + if (!kv_response.ParseFromString(*resp)) { + return Items(); + } + + return kv_response.items(); + } + protected: - MockStorage* mock_storage_ptr_; + Storage* storage_ptr_; private: std::unique_ptr impl_; @@ -133,35 +243,107 @@ class KVExecutorTest : public Test { TEST_F(KVExecutorTest, SetValue) { std::map data; - EXPECT_CALL(*mock_storage_ptr_, SetValue("test_key", "test_value")) - .WillOnce(Invoke([&](const std::string& key, const std::string& value) { - data[key] = value; - return 0; - })); - - EXPECT_CALL(*mock_storage_ptr_, GetValue("test_key")) - .WillOnce(Invoke([&](const std::string& key) { - std::string ret = data[key]; - return ret; - })); - - EXPECT_CALL(*mock_storage_ptr_, GetAllValues()) - .WillOnce(Return("[]")) - .WillOnce(Return("[test_value]")); - - EXPECT_CALL(*mock_storage_ptr_, GetRange("a", "z")) - .WillOnce(Return("[test_value]")); - EXPECT_EQ(GetAllValues(), "[]"); EXPECT_EQ(Set("test_key", "test_value"), 0); EXPECT_EQ(Get("test_key"), "test_value"); - // GetAllValues and GetRange may be out of order for in-memory, so we test up to - // 1 key-value pair + // GetAllValues and GetRange may be out of order for in-memory, so we test up + // to 1 key-value pair EXPECT_EQ(GetAllValues(), "[test_value]"); EXPECT_EQ(GetRange("a", "z"), "[test_value]"); } +TEST_F(KVExecutorTest, SetValueWithVersion) { + std::map data; + + { + EXPECT_EQ(Set("test_key", "test_value", 0), 0); + ValueInfo expected_info; + expected_info.set_value("test_value"); + expected_info.set_version(1); + EXPECT_THAT(Get("test_key", 1), EqualsProto(expected_info)); + } + + { + EXPECT_EQ(Set("test_key", "test_value1", 1), 0); + ValueInfo expected_info; + expected_info.set_value("test_value1"); + expected_info.set_version(2); + EXPECT_THAT(Get("test_key", 2), EqualsProto(expected_info)); + } + { + EXPECT_EQ(Set("test_key", "test_value1", 1), 0); + ValueInfo expected_info; + expected_info.set_value("test_value"); + expected_info.set_version(1); + EXPECT_THAT(Get("test_key", 1), EqualsProto(expected_info)); + } + + { + EXPECT_EQ(Set("test_key1", "test_key1", 0), 0); + ValueInfo expected_info; + expected_info.set_value("test_key1"); + expected_info.set_version(1); + EXPECT_THAT(Get("test_key1", 1), EqualsProto(expected_info)); + + ValueInfo expected_info2; + expected_info2.set_value("test_value"); + expected_info2.set_version(1); + EXPECT_THAT(Get("test_key", 1), EqualsProto(expected_info2)); + + ValueInfo expected_info3; + expected_info3.set_value("test_value1"); + expected_info3.set_version(2); + EXPECT_THAT(Get("test_key", 0), EqualsProto(expected_info3)); + } + + { + Items items; + { + Item* item = items.add_item(); + item->set_key("test_key"); + item->mutable_value_info()->set_value("test_value1"); + item->mutable_value_info()->set_version(2); + } + { + Item* item = items.add_item(); + item->set_key("test_key1"); + item->mutable_value_info()->set_value("test_key1"); + item->mutable_value_info()->set_version(1); + } + + EXPECT_THAT(GetAllItems(), EqualsProto(items)); + } + + { + Items items; + { + Item* item = items.add_item(); + item->set_key("test_key"); + item->mutable_value_info()->set_value("test_value1"); + item->mutable_value_info()->set_version(2); + } + EXPECT_THAT(GetKeyRange("test_key", "test_key"), EqualsProto(items)); + } + + { + Items items; + { + Item* item = items.add_item(); + item->set_key("test_key"); + item->mutable_value_info()->set_value("test_value1"); + item->mutable_value_info()->set_version(2); + } + { + Item* item = items.add_item(); + item->set_key("test_key"); + item->mutable_value_info()->set_value("test_value"); + item->mutable_value_info()->set_version(1); + } + EXPECT_THAT(GetHistory("test_key", 0, 2), EqualsProto(items)); + } +} + } // namespace } // namespace resdb diff --git a/interface/kv/kv_client.cpp b/interface/kv/kv_client.cpp index 53a304ef5..9d111fb89 100644 --- a/interface/kv/kv_client.cpp +++ b/interface/kv/kv_client.cpp @@ -27,8 +27,6 @@ #include -#include "proto/kv/kv.pb.h" - namespace resdb { KVClient::KVClient(const ResDBConfig& config) @@ -82,4 +80,75 @@ std::unique_ptr KVClient::GetRange(const std::string& min_key, return std::make_unique(response.value()); } +int KVClient::Set(const std::string& key, const std::string& data, + int version) { + KVRequest request; + request.set_cmd(KVRequest::SET_WITH_VERSION); + request.set_key(key); + request.set_value(data); + request.set_version(version); + return SendRequest(request); +} + +std::unique_ptr KVClient::Get(const std::string& key, int version) { + KVRequest request; + request.set_cmd(KVRequest::GET_WITH_VERSION); + request.set_key(key); + request.set_version(version); + KVResponse response; + int ret = SendRequest(request, &response); + if (ret != 0) { + LOG(ERROR) << "send request fail, ret:" << ret; + return nullptr; + } + return std::make_unique(response.value_info()); +} + +std::unique_ptr KVClient::GetKeyRange(const std::string& min_key, + const std::string& max_key) { + KVRequest request; + request.set_cmd(KVRequest::GET_KEY_RANGE); + request.set_min_key(min_key); + request.set_max_key(max_key); + KVResponse response; + int ret = SendRequest(request, &response); + if (ret != 0) { + LOG(ERROR) << "send request fail, ret:" << ret; + return nullptr; + } + return std::make_unique(response.items()); +} + +std::unique_ptr KVClient::GetKeyHistory(const std::string& key, + int min_version, + int max_version) { + KVRequest request; + request.set_cmd(KVRequest::GET_HISTORY); + request.set_key(key); + request.set_min_version(min_version); + request.set_max_version(max_version); + KVResponse response; + int ret = SendRequest(request, &response); + if (ret != 0) { + LOG(ERROR) << "send request fail, ret:" << ret; + return nullptr; + } + return std::make_unique(response.items()); +} + +std::unique_ptr KVClient::GetKeyTopHistory(const std::string& key, + int top_number) { + KVRequest request; + request.set_cmd(KVRequest::GET_TOP); + request.set_key(key); + request.set_top_number(top_number); + KVResponse response; + int ret = SendRequest(request, &response); + if (ret != 0) { + LOG(ERROR) << "send request fail, ret:" << ret; + return nullptr; + } + return std::make_unique(response.items()); +} + } // namespace resdb diff --git a/interface/kv/kv_client.h b/interface/kv/kv_client.h index 87c4a00d0..09140c935 100644 --- a/interface/kv/kv_client.h +++ b/interface/kv/kv_client.h @@ -26,6 +26,7 @@ #pragma once #include "interface/rdbc/transaction_constructor.h" +#include "proto/kv/kv.pb.h" namespace resdb { @@ -34,6 +35,33 @@ class KVClient : public TransactionConstructor { public: KVClient(const ResDBConfig& config); + //Version-based interfaces. + // Obtain the current version before setting a new data + int Set(const std::string& key, const std::string& data, int version); + + // Obtain the value with a specific version. + // If the version parameter is zero, it will return the data with the current version in the + // database. ValueInfo contains the version and its version. + // Return nullptr if there is an error. + std::unique_ptr Get(const std::string& key, int version); + + // Obtain the latest values of the keys within [min_key, max_key]. + // Keys should be comparable. + std::unique_ptr GetKeyRange(const std::string& min_key, + const std::string& max_key); + + // Obtain the histories of `key` with the versions in [min_version, + // max_version] + std::unique_ptr GetKeyHistory(const std::string& key, int min_version, + int max_version); + + // Obtain the top `top_number` histories of the `key`. + std::unique_ptr GetKeyTopHistory(const std::string& key, + int top_number); + + // Non-version-based Interfaces. + // These interfaces are not compatible with the version-based interfaces + // above. int Set(const std::string& key, const std::string& data); std::unique_ptr Get(const std::string& key); std::unique_ptr GetAllValues(); diff --git a/platform/consensus/ordering/pbft/BUILD b/platform/consensus/ordering/pbft/BUILD index 300c96a90..ce59cb9f7 100644 --- a/platform/consensus/ordering/pbft/BUILD +++ b/platform/consensus/ordering/pbft/BUILD @@ -61,7 +61,7 @@ cc_library( ":lock_free_collector_pool", ":transaction_collector", ":transaction_utils", - "//chain/storage:txn_memory_db", + "//chain/state:chain_state", "//executor/common:transaction_manager", "//platform/config:resdb_config", "//platform/networkstrate:server_comm", @@ -114,7 +114,7 @@ cc_library( hdrs = ["checkpoint_manager.h"], deps = [ ":transaction_utils", - "//chain/storage:txn_memory_db", + "//chain/state:chain_state", "//common/crypto:signature_verifier", "//interface/common:resdb_txn_accessor", "//platform/config:resdb_config", diff --git a/platform/consensus/ordering/pbft/checkpoint_manager.cpp b/platform/consensus/ordering/pbft/checkpoint_manager.cpp index cc8815633..cf86a9b28 100644 --- a/platform/consensus/ordering/pbft/checkpoint_manager.cpp +++ b/platform/consensus/ordering/pbft/checkpoint_manager.cpp @@ -37,7 +37,7 @@ CheckPointManager::CheckPointManager(const ResDBConfig& config, SignatureVerifier* verifier) : config_(config), replica_communicator_(replica_communicator), - txn_db_(std::make_unique()), + txn_db_(std::make_unique()), verifier_(verifier), stop_(false), txn_accessor_(config), @@ -71,7 +71,7 @@ std::string GetHash(const std::string& h1, const std::string& h2) { return SignatureVerifier::CalculateHash(h1 + h2); } -TxnMemoryDB* CheckPointManager::GetTxnDB() { return txn_db_.get(); } +ChainState* CheckPointManager::GetTxnDB() { return txn_db_.get(); } uint64_t CheckPointManager::GetMaxTxnSeq() { return txn_db_->GetMaxSeq(); } @@ -105,7 +105,7 @@ bool CheckPointManager::IsValidCheckpointProof( senders.insert(signature.node_id()); } - return (senders.size() >= config_.GetMinDataReceiveNum()) || + return (static_cast(senders.size()) >= config_.GetMinDataReceiveNum()) || (stable_ckpt.seq() == 0 && senders.size() == 0); } @@ -171,7 +171,6 @@ bool CheckPointManager::Wait() { void CheckPointManager::UpdateStableCheckPointStatus() { uint64_t last_committable_seq = 0; - int water_mark = config_.GetCheckPointWaterMark(); while (!stop_) { if (!Wait()) { continue; diff --git a/platform/consensus/ordering/pbft/checkpoint_manager.h b/platform/consensus/ordering/pbft/checkpoint_manager.h index cbde9ada5..e2085e2e9 100644 --- a/platform/consensus/ordering/pbft/checkpoint_manager.h +++ b/platform/consensus/ordering/pbft/checkpoint_manager.h @@ -27,7 +27,7 @@ #include -#include "chain/storage/txn_memory_db.h" +#include "chain/state/chain_state.h" #include "common/crypto/signature_verifier.h" #include "interface/common/resdb_txn_accessor.h" #include "platform/config/resdb_config.h" @@ -47,7 +47,7 @@ class CheckPointManager : public CheckPoint { SignatureVerifier* verifier); virtual ~CheckPointManager(); - TxnMemoryDB* GetTxnDB(); + ChainState* GetTxnDB(); uint64_t GetMaxTxnSeq(); void AddCommitData(std::unique_ptr request); @@ -92,7 +92,7 @@ class CheckPointManager : public CheckPoint { protected: ResDBConfig config_; ReplicaCommunicator* replica_communicator_; - std::unique_ptr txn_db_; + std::unique_ptr txn_db_; std::thread checkpoint_thread_, stable_checkpoint_thread_; SignatureVerifier* verifier_; std::atomic stop_; diff --git a/platform/consensus/ordering/pbft/commitment.cpp b/platform/consensus/ordering/pbft/commitment.cpp index d30add520..4c555c1ce 100644 --- a/platform/consensus/ordering/pbft/commitment.cpp +++ b/platform/consensus/ordering/pbft/commitment.cpp @@ -160,7 +160,8 @@ int Commitment::ProcessProposeMsg(std::unique_ptr context, return -2; } if (request->is_recovery()) { - if (request->seq() >= message_manager_->GetNextSeq()) { + if (static_cast(request->seq()) >= + message_manager_->GetNextSeq()) { message_manager_->SetNextSeq(request->seq() + 1); } return message_manager_->AddConsensusMsg(context->signature, diff --git a/platform/consensus/ordering/pbft/message_manager.h b/platform/consensus/ordering/pbft/message_manager.h index 667dfe831..d9efa47a1 100644 --- a/platform/consensus/ordering/pbft/message_manager.h +++ b/platform/consensus/ordering/pbft/message_manager.h @@ -32,7 +32,7 @@ #include #include -#include "chain/storage/txn_memory_db.h" +#include "chain/state/chain_state.h" #include "executor/common/transaction_manager.h" #include "platform/common/queue/lock_free_queue.h" #include "platform/config/resdb_config.h" @@ -130,7 +130,7 @@ class MessageManager { uint64_t next_seq_ = 1; LockFreeQueue queue_; - TxnMemoryDB* txn_db_; + ChainState* txn_db_; SystemInfo* system_info_; CheckPointManager* checkpoint_manager_; std::map>> diff --git a/platform/consensus/recovery/recovery_test.cpp b/platform/consensus/recovery/recovery_test.cpp index f02cb9dff..b3951efd7 100644 --- a/platform/consensus/recovery/recovery_test.cpp +++ b/platform/consensus/recovery/recovery_test.cpp @@ -112,7 +112,7 @@ TEST_F(RecoveryTest, ReadLog) { EXPECT_EQ(list.size(), expected_types.size()); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } } @@ -153,7 +153,7 @@ TEST_F(RecoveryTest, ReadLog_FlushOnce) { EXPECT_EQ(list.size(), expected_types.size()); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } } @@ -219,7 +219,7 @@ TEST_F(RecoveryTest, CheckPoint) { EXPECT_EQ(list.size(), types.size() * 14); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } } @@ -296,7 +296,7 @@ TEST_F(RecoveryTest, CheckPoint2) { EXPECT_EQ(list.size(), types.size() * 14); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } @@ -333,7 +333,7 @@ TEST_F(RecoveryTest, CheckPoint2) { EXPECT_EQ(list.size(), types.size() * 9); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } EXPECT_EQ(recovery.GetMinSeq(), 30); @@ -415,7 +415,7 @@ TEST_F(RecoveryTest, SystemInfo) { EXPECT_EQ(list.size(), types.size() * 14); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } @@ -455,7 +455,7 @@ TEST_F(RecoveryTest, SystemInfo) { EXPECT_EQ(data.primary_id(), 2); EXPECT_EQ(list.size(), types.size() * 9); - for (int i = 0; i < expected_types.size(); ++i) { + for (size_t i = 0; i < expected_types.size(); ++i) { EXPECT_EQ(list[i].type(), expected_types[i]); } EXPECT_EQ(recovery.GetMinSeq(), 30); diff --git a/platform/proto/BUILD b/platform/proto/BUILD index 7b26b2abf..c19b351cd 100644 --- a/platform/proto/BUILD +++ b/platform/proto/BUILD @@ -19,7 +19,8 @@ proto_library( name = "replica_info_proto", srcs = ["replica_info.proto"], deps = [ - ":durable_proto", + "//chain/storage/proto:leveldb_config_proto", + "//chain/storage/proto:rocksdb_config_proto", "//common/proto:signature_info_proto", ], ) @@ -34,26 +35,15 @@ cc_proto_library( python_proto_library( name = "replica_info_py_proto", protos = [ - ":durable_proto", ":replica_info_proto", + "//chain/storage/proto:leveldb_config_proto", + "//chain/storage/proto:rocksdb_config_proto", ], deps = [ "//common/proto:signature_info_py_proto", ], ) -proto_library( - name = "durable_proto", - srcs = ["durable.proto"], -) - -cc_proto_library( - name = "durable_cc_proto", - deps = [ - ":durable_proto", - ], -) - proto_library( name = "resdb_proto", srcs = ["resdb.proto"], diff --git a/platform/proto/durable.proto b/platform/proto/durable.proto deleted file mode 100644 index 24823a021..000000000 --- a/platform/proto/durable.proto +++ /dev/null @@ -1,18 +0,0 @@ -syntax = "proto3"; - -package resdb; - -message RocksDBInfo { - uint32 num_threads = 2; - uint32 write_buffer_size_mb = 3; - uint32 write_batch_size = 4; - string path = 5; - bool generate_unique_pathnames = 6; -} - -message LevelDBInfo { - uint32 write_buffer_size_mb = 2; - uint32 write_batch_size = 3; - string path = 4; - bool generate_unique_pathnames = 5; -} diff --git a/platform/proto/replica_info.proto b/platform/proto/replica_info.proto index ebfd53731..9c81dade8 100644 --- a/platform/proto/replica_info.proto +++ b/platform/proto/replica_info.proto @@ -3,7 +3,8 @@ syntax = "proto3"; package resdb; import "common/proto/signature_info.proto"; -import "platform/proto/durable.proto"; +import "chain/storage/proto/leveldb_config.proto"; +import "chain/storage/proto/rocksdb_config.proto"; message ReplicaInfo { int64 id = 1; @@ -20,8 +21,8 @@ message RegionInfo { message ResConfigData{ repeated RegionInfo region = 1; int32 self_region_id = 2; - optional RocksDBInfo rocksdb_info = 3; - optional LevelDBInfo leveldb_info = 4; + optional storage.RocksDBInfo rocksdb_info = 3; + optional storage.LevelDBInfo leveldb_info = 4; optional bool enable_viewchange = 5; optional int32 view_change_timeout_ms = 10; optional bool not_need_signature = 6; // when delivering messages, it should be signed or not. diff --git a/platform/test/resdb_test.cpp b/platform/test/resdb_test.cpp index 0152a0803..89fcdac00 100644 --- a/platform/test/resdb_test.cpp +++ b/platform/test/resdb_test.cpp @@ -109,7 +109,7 @@ class ResDBTest : public Test { void WaitExecutorDone(int received_num) { for (auto executor : executors_) { - while (executor->GetSeqs().size() < received_num) { + while (static_cast(executor->GetSeqs().size()) < received_num) { usleep(10000); } } diff --git a/proto/kv/kv.proto b/proto/kv/kv.proto index 9cfb4a64b..f07523fe8 100644 --- a/proto/kv/kv.proto +++ b/proto/kv/kv.proto @@ -9,14 +9,45 @@ message KVRequest { GET = 2; GETALLVALUES = 3; GETRANGE = 4; + SET_WITH_VERSION = 5; + GET_WITH_VERSION = 6; + GET_ALL_ITEMS = 7; + GET_KEY_RANGE = 8; + GET_HISTORY = 9; + GET_TOP = 10; } CMD cmd = 1; string key = 2; bytes value = 3; + int32 version = 4; + // For get key range + string min_key = 5; + string max_key = 6; + // For get history for a key + int32 min_version = 7; + int32 max_version = 8; + // For top history + int32 top_number = 9; +} + +message ValueInfo { + bytes value = 2; + int32 version = 3; +} + +message Item { + string key = 1; + ValueInfo value_info = 2; +} + +message Items { + repeated Item item = 1; } message KVResponse { string key = 1; bytes value = 2; + ValueInfo value_info = 3; + Items items = 4; } diff --git a/service/kv/BUILD b/service/kv/BUILD index eed3f0be8..56b45ae16 100644 --- a/service/kv/BUILD +++ b/service/kv/BUILD @@ -6,8 +6,8 @@ cc_binary( name = "kv_service", srcs = ["kv_service.cpp"], copts = select({ - "//executor/kv:enable_leveldb_setting": ["-DENABLE_LEVELDB"], - "//executor/kv:enable_rocksdb_setting": ["-DENABLE_ROCKSDB"], + "//chain/storage/setting:enable_leveldb_setting": ["-DENABLE_LEVELDB"], + "//chain/storage/setting:enable_rocksdb_setting": ["-DENABLE_ROCKSDB"], "//conditions:default": [], }), deps = [ @@ -15,11 +15,11 @@ cc_binary( "//executor/kv:kv_executor", "//service/utils:server_factory", "//common:comm", - "//chain/state:chain_state", "//proto/kv:kv_cc_proto", + "//chain/storage:memory_db", ] + select({ - "//executor/kv:enable_leveldb_setting": ["//storage:res_leveldb"], - "//executor/kv:enable_rocksdb_setting": ["//storage:res_rocksdb"], + "//chain/storage/setting:enable_leveldb_setting": ["//chain/storage:leveldb"], + "//chain/storage/setting:enable_rocksdb_setting": ["//chain/storage:rocksdb"], "//conditions:default": [], }), ) diff --git a/service/kv/kv_service.cpp b/service/kv/kv_service.cpp index 2ab9d5ff4..028837df1 100644 --- a/service/kv/kv_service.cpp +++ b/service/kv/kv_service.cpp @@ -25,40 +25,39 @@ #include -#include "chain/state/chain_state.h" +#include "chain/storage/memory_db.h" #include "executor/kv/kv_executor.h" #include "platform/config/resdb_config_utils.h" #include "platform/statistic/stats.h" #include "service/utils/server_factory.h" #ifdef ENABLE_LEVELDB -#include "chain/storage/res_leveldb.h" +#include "chain/storage/leveldb.h" #endif #ifdef ENABLE_ROCKSDB -#include "chain/storage/res_rocksdb.h" +#include "chain/storage/rocksdb.h" #endif using namespace resdb; +using namespace resdb::storage; void ShowUsage() { printf(" [logging_dir]\n"); } -std::unique_ptr NewState(const std::string& cert_file, - const ResConfigData& config_data) { - std::unique_ptr storage = nullptr; - +std::unique_ptr NewStorage(const std::string& db_path, + const ResConfigData& config_data) { #ifdef ENABLE_ROCKSDB - storage = NewResRocksDB(cert_file.c_str(), config_data); LOG(INFO) << "use rocksdb storage."; + return NewResRocksDB(db_path, config_data); #endif #ifdef ENABLE_LEVELDB - storage = NewResLevelDB(cert_file.c_str(), config_data); LOG(INFO) << "use leveldb storage."; + return NewResLevelDB(db_path, config_data); #endif - std::unique_ptr state = - std::make_unique(std::move(storage)); - return state; + + LOG(INFO) << "use memory storage."; + return NewMemoryDB(); } int main(int argc, char** argv) { @@ -66,6 +65,7 @@ int main(int argc, char** argv) { ShowUsage(); exit(0); } + google::InitGoogleLogging(argv[0]); char* config_file = argv[1]; char* private_key_file = argv[2]; @@ -84,8 +84,11 @@ int main(int argc, char** argv) { GenerateResDBConfig(config_file, private_key_file, cert_file); ResConfigData config_data = config->GetConfigData(); + std::string db_path = std::to_string(config->GetSelfInfo().port()) + "_db/"; + LOG(INFO) << "db path:" << db_path; + auto server = GenerateResDBServer( config_file, private_key_file, cert_file, - std::make_unique(NewState(cert_file, config_data)), nullptr); + std::make_unique(NewStorage(db_path, config_data)), nullptr); server->Run(); } diff --git a/service/tools/config/server/server.config b/service/tools/config/server/server.config index e75cbf7cd..20a8b7711 100644 --- a/service/tools/config/server/server.config +++ b/service/tools/config/server/server.config @@ -27,12 +27,10 @@ num_threads:1, write_buffer_size_mb:32, write_batch_size:1, - generate_unique_pathnames:true, }, leveldb_info : { write_buffer_size_mb:128, write_batch_size:1, - generate_unique_pathnames:true, }, require_txn_validation:false, } diff --git a/service/tools/kv/api_tools/kv_service_tools.cpp b/service/tools/kv/api_tools/kv_service_tools.cpp index 4cddb2bee..5ab65ba77 100644 --- a/service/tools/kv/api_tools/kv_service_tools.cpp +++ b/service/tools/kv/api_tools/kv_service_tools.cpp @@ -24,6 +24,7 @@ */ #include +#include #include #include #include @@ -40,58 +41,191 @@ using resdb::KVClient; using resdb::ReplicaInfo; using resdb::ResDBConfig; +void ShowUsage() { + printf( + "--config: config path\n" + "--cmd " + "set/get/set_with_version/get_with_version/get_key_range/" + "get_key_range_with_version/get_top/get_history\n" + "--key key\n" + "--value value, if cmd is a get operation\n" + "--version version of the value, if cmd is vesion based\n" + "--min_key the min key if cmd is get_key_range\n" + "--max_key the max key if cmd is get_key_range\n" + "--min_version, if cmd is get_history\n" + "--max_version, if cmd is get_history\n" + "--top, if cmd is get_top\n" + "\n" + "More examples can be found from README.\n"); +} + +static struct option long_options[] = { + {"help", no_argument, NULL, 'h'}, + {"config", required_argument, NULL, 'c'}, + {"cmd", required_argument, NULL, 'f'}, + {"key", required_argument, NULL, 'K'}, + {"value", required_argument, NULL, 'V'}, + {"version", required_argument, NULL, 'v'}, + {"min_version", required_argument, NULL, 's'}, + {"max_version", required_argument, NULL, 'S'}, + {"min_key", required_argument, NULL, 'y'}, + {"max_key", required_argument, NULL, 'Y'}, + {"top", required_argument, NULL, 't'}, +}; + int main(int argc, char** argv) { if (argc < 3) { - printf( - " (set/get/getallvalues/getrange), [key] " - "[value/key2]\n"); + ShowUsage(); return 0; } - std::string client_config_file = argv[1]; - std::string cmd = argv[2]; std::string key; - if (cmd != "getallvalues") { - key = argv[3]; - } + int version = -1; + int option_index = 0; + int min_version = -1, max_version = -1; + std::string min_key, max_key; std::string value; - if (cmd == "set") { - value = argv[4]; - } + std::string client_config_file; + int top = 0; + char c; + std::string cmd; - std::string key2; - if (cmd == "getrange") { - key2 = argv[4]; + while ((c = getopt_long(argc, argv, "h", long_options, &option_index)) != + -1) { + switch (c) { + case 'c': + client_config_file = optarg; + break; + case 'f': + cmd = optarg; + break; + case 'K': + key = optarg; + break; + case 'V': + value = optarg; + break; + case 'v': + version = strtoull(optarg, NULL, 10); + break; + case 's': + min_version = strtoull(optarg, NULL, 10); + break; + case 'S': + max_version = strtoull(optarg, NULL, 10); + break; + case 'y': + min_key = optarg; + break; + case 'Y': + max_key = optarg; + break; + case 't': + top = strtoull(optarg, NULL, 10); + break; + case 'h': + ShowUsage(); + break; + } } ResDBConfig config = GenerateResDBConfig(client_config_file); config.SetClientTimeoutMs(100000); - KVClient client(config); - - if (cmd == "set") { + if (cmd == "set_with_version") { + if (key.empty() || value.empty() || version < 0) { + ShowUsage(); + return 0; + } + int ret = client.Set(key, value, version); + printf("set key = %s, value = %s, version = %d done, ret = %d\n", + key.c_str(), value.c_str(), version, ret); + if (ret == 0) { + usleep(100000); + auto res = client.Get(key, 0); + if (res != nullptr) { + printf("current value = %s\n", res->DebugString().c_str()); + } else { + printf("get value fail\n"); + } + } + } else if (cmd == "set") { + if (key.empty() || value.empty()) { + ShowUsage(); + return 0; + } int ret = client.Set(key, value); - printf("client set ret = %d\n", ret); + printf("set key = %s, value = %s done, ret = %d\n", key.c_str(), + value.c_str(), ret); + } else if (cmd == "get_with_version") { + auto res = client.Get(key, version); + if (res != nullptr) { + printf("get key = %s, value = %s\n", key.c_str(), + res->DebugString().c_str()); + } else { + printf("get value fail\n"); + } } else if (cmd == "get") { auto res = client.Get(key); if (res != nullptr) { - printf("client get value = %s\n", res->c_str()); + printf("get key = %s value = %s\n", key.c_str(), res->c_str()); + } else { + printf("get value fail\n"); + } + } else if (cmd == "get_top") { + auto res = client.GetKeyTopHistory(key, top); + if (res != nullptr) { + printf("key = %s, top %d\n value = %s\n", key.c_str(), top, + res->DebugString().c_str()); } else { - printf("client get value fail\n"); + printf("get key = %s top %d value fail\n", key.c_str(), top); + } + } else if (cmd == "get_history") { + if (key.empty() || min_version < 0 || max_version < 0 || + max_version < min_version) { + ShowUsage(); + return 0; } - } else if (cmd == "getallvalues") { - auto res = client.GetAllValues(); + auto res = client.GetKeyHistory(key, min_version, max_version); if (res != nullptr) { - printf("client getallvalues value = %s\n", res->c_str()); + printf( + "get history key = %s, min version = %d, max version = %d\n value = " + "%s\n", + key.c_str(), min_version, max_version, res->DebugString().c_str()); } else { - printf("client getallvalues value fail\n"); + printf( + "get history key = %s, min version = %d, max version = %d value " + "fail\n", + key.c_str(), min_version, max_version); + } + } else if (cmd == "get_key_range") { + if (min_key.empty() || max_key.empty() || min_key > max_key) { + ShowUsage(); + return 0; + } + auto res = client.GetRange(min_key, max_key); + if (res != nullptr) { + printf("getrange min key = %s, max key = %s\n value = %s\n", + min_key.c_str(), max_key.c_str(), (*res).c_str()); + } else { + printf("getrange value fail, min key = %s, max key = %s\n", + min_key.c_str(), max_key.c_str()); + } + } else if (cmd == "get_key_range_with_version") { + if (min_key.empty() || max_key.empty() || min_key > max_key) { + ShowUsage(); + return 0; } - } else if (cmd == "getrange") { - auto res = client.GetRange(key, key2); + printf("min key = %s max key = %s\n", min_key.c_str(), max_key.c_str()); + auto res = client.GetKeyRange(min_key, max_key); if (res != nullptr) { - printf("client getrange value = %s\n", res->c_str()); + printf("getrange min key = %s, max key = %s\n value = %s\n", + min_key.c_str(), max_key.c_str(), res->DebugString().c_str()); } else { - printf("client getrange value fail\n"); + printf("getrange value fail, min key = %s, max key = %s\n", + min_key.c_str(), max_key.c_str()); } + } else { + ShowUsage(); } } diff --git a/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp b/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp index f6adf0628..b9e691917 100644 --- a/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp +++ b/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp @@ -46,9 +46,10 @@ void ShowUsage() { } void Transfer(UTXOClient* client, int64_t transaction_id, - const std::string& address, + const std::string& address, const std::vector& to_address, - const std::vector& values, const std::string& private_key, + const std::vector& values, + const std::string& private_key, const std::vector& to_pub_key) { if (private_key.empty() || to_pub_key.empty()) { printf("no private key or public key\n"); @@ -61,15 +62,16 @@ void Transfer(UTXOClient* client, int64_t transaction_id, in->set_out_idx(0); nonce += transaction_id; - for(int i = 0; i < to_address.size();++i){ + for (size_t i = 0; i < to_address.size(); ++i) { UTXOOut* out = utxo.add_out(); out->set_address(to_address[i]); out->set_value(values[i]); out->set_pub_key(to_pub_key[i]); utxo.set_address(address); - utxo.set_sig(resdb::utils::ECDSASignString(private_key, - address + std::to_string(nonce))); - LOG(ERROR)<<"transfer from:"<Transfer(utxo); @@ -91,35 +93,34 @@ void GetWallet(UTXOClient* client, const std::string& address) { LOG(ERROR) << "address:" << address << " get wallet value:" << ret; } -std::vector ParseString(std::string str){ +std::vector ParseString(std::string str) { std::vector ret; - while(true){ + while (true) { size_t pos = str.find(","); - if(pos == std::string::npos){ + if (pos == std::string::npos) { ret.push_back(str); break; } - ret.push_back(str.substr(0,pos)); - str = str.substr(pos+1); + ret.push_back(str.substr(0, pos)); + str = str.substr(pos + 1); } return ret; } -std::vector ParseValue(std::string str){ +std::vector ParseValue(std::string str) { std::vector ret; - while(true){ + while (true) { size_t pos = str.find(","); - if(pos == std::string::npos){ + if (pos == std::string::npos) { ret.push_back(strtoull(str.c_str(), NULL, 10)); break; } - ret.push_back(strtoull(str.substr(0,pos).c_str(), NULL, 10)); - str = str.substr(pos+1); + ret.push_back(strtoull(str.substr(0, pos).c_str(), NULL, 10)); + str = str.substr(pos + 1); } return ret; } - int main(int argc, char** argv) { if (argc < 3) { printf("-d -c [config]\n"); @@ -177,8 +178,8 @@ int main(int argc, char** argv) { config.SetClientTimeoutMs(100000); UTXOClient client(config); if (cmd == "transfer") { - Transfer(&client, transaction_id, address, ParseString(to_address), ParseValue(value), private_key, - ParseString(to_pub_key)); + Transfer(&client, transaction_id, address, ParseString(to_address), + ParseValue(value), private_key, ParseString(to_pub_key)); } else if (cmd == "list") { GetList(&client, end_id, num); } else if (cmd == "wallet") {