From 398b979138e469cb703f38df90245de67f7cb1fe Mon Sep 17 00:00:00 2001 From: JeffXiesk <12011206@mail.sustech.edu.cn> Date: Thu, 2 Jan 2025 23:57:43 +0000 Subject: [PATCH] Stable Fides Version --- .asf.yaml | 40 + .bazelversion | 1 + .github/workflows/build-push.yml | 4 +- .gitignore | 5 + CHANGELOG.md | 8 + Docker/Dockerfile | 2 + Docker/Dockerfile_mac | 2 + Fides_extended_version.pdf | Bin 0 -> 1062036 bytes INSTALL.sh | 15 +- NOTICE | 5 + README.md | 254 +- WORKSPACE | 34 +- benchmark/protocols/fides/BUILD | 18 + .../protocols/fides/kv_server_performance.cpp | 261 + .../protocols/fides/kv_service_tools.cpp | 57 + benchmark/protocols/pbft/BUILD | 1 + .../protocols/pbft/kv_server_performance.cpp | 39 +- benchmark/protocols/pbft/kv_service_tools.cpp | 34 +- benchmark/protocols/poe/BUILD | 16 + .../protocols/poe/kv_server_performance.cpp | 89 + benchmark/protocols/poe/kv_service_tools.cpp | 57 + chain/state/BUILD | 2 +- chain/state/chain_state.cpp | 98 +- chain/state/chain_state.h | 54 +- chain/state/chain_state_test.cpp | 75 +- chain/storage/BUILD | 50 +- chain/storage/README.md | 13 - chain/storage/kv_storage_test.cpp | 232 + chain/storage/leveldb.cpp | 290 + chain/storage/leveldb.h | 81 + chain/storage/memory_db.cpp | 170 + chain/storage/memory_db.h | 88 + chain/storage/mock_storage.h | 52 +- chain/storage/proto/BUILD | 36 + chain/storage/proto/kv.proto | 13 + chain/storage/proto/leveldb_config.proto | 9 + chain/storage/proto/rocksdb_config.proto | 11 + chain/storage/res_leveldb.cpp | 169 - chain/storage/res_leveldb_test.cpp | 111 - chain/storage/res_rocksdb.cpp | 171 - chain/storage/res_rocksdb_test.cpp | 109 - chain/storage/rocksdb.cpp | 291 + chain/storage/rocksdb.h | 80 + chain/storage/setting/BUILD | 31 + chain/storage/storage.h | 65 +- common/crypto/signature_utils.cpp | 12 +- documents/doxygen/Doxyfile | 8 +- documents/doxygen/doxygen_html_style.css | 4 + documents/doxygen/header | 60 + documents/doxygen/logo.png | Bin 803 -> 71672 bytes enclave/BUILD | 37 + enclave/sgx_cpp_args.h | 160 + enclave/sgx_cpp_u.c | 7997 +++++++++++++++++ enclave/sgx_cpp_u.h | 559 ++ enclave/sgxcode/CMakeLists.txt | 56 + enclave/sgxcode/Makefile | 24 + enclave/sgxcode/README.md | 138 + enclave/sgxcode/enclave/CMakeLists.txt | 42 + enclave/sgxcode/enclave/Makefile | 53 + enclave/sgxcode/enclave/common/dispatcher.cpp | 7 + enclave/sgxcode/enclave/common/dispatcher.h | 87 + enclave/sgxcode/enclave/common/ecalls.cpp | 93 + .../enclave/common/file-encryptor.conf | 10 + enclave/sgxcode/enclave/common/random.h | 0 enclave/sgxcode/enclave/common/trace.h | 5 + .../enclave/decryptor_src/decryptor copy.cpp | 139 + .../enclave/decryptor_src/decryptor.cpp | 224 + enclave/sgxcode/enclave/random_src/random.cpp | 63 + enclave/sgxcode/enclave/sgx_cpp.edl | 45 + .../simulated_counter.cpp | 59 + enclave/sgxcode/host/CMakeLists.txt | 25 + enclave/sgxcode/host/Makefile | 24 + enclave/sgxcode/host/host.cpp | 278 + entrypoint.sh | 4 + executor/common/custom_query.h | 34 +- executor/common/mock_transaction_manager.h | 34 +- executor/common/transaction_manager.cpp | 78 +- executor/common/transaction_manager.h | 44 +- .../contract/executor/contract_executor.cpp | 34 +- .../contract/executor/contract_executor.h | 34 +- .../executor/contract_executor_test.cpp | 34 +- executor/contract/manager/address_manager.cpp | 34 +- executor/contract/manager/address_manager.h | 34 +- .../contract/manager/address_manager_test.cpp | 34 +- .../contract/manager/contract_manager.cpp | 34 +- executor/contract/manager/contract_manager.h | 34 +- .../manager/contract_manager_test.cpp | 34 +- executor/contract/manager/utils.h | 34 +- executor/kv/BUILD | 32 +- executor/kv/kv_executor.cpp | 180 +- executor/kv/kv_executor.h | 58 +- executor/kv/kv_executor_test.cpp | 276 +- executor/utxo/executor/utxo_executor.cpp | 34 +- executor/utxo/executor/utxo_executor.h | 34 +- executor/utxo/executor/utxo_executor_test.cpp | 34 +- executor/utxo/manager/transaction.cpp | 34 +- executor/utxo/manager/transaction.h | 34 +- executor/utxo/manager/transaction_test.cpp | 34 +- executor/utxo/manager/tx_mempool.cpp | 34 +- executor/utxo/manager/tx_mempool.h | 34 +- executor/utxo/manager/tx_mempool_test.cpp | 34 +- executor/utxo/manager/wallet.cpp | 34 +- executor/utxo/manager/wallet.h | 34 +- executor/utxo/manager/wallet_test.cpp | 34 +- img/apache-incubator.png | Bin 0 -> 30207 bytes img/apache-resdb.png | Bin 0 -> 98785 bytes img/resdb-v2.png | Bin 0 -> 41784 bytes img/resdb.png | Bin 0 -> 36015 bytes interface/common/mock_resdb_txn_accessor.h | 34 +- interface/common/resdb_state_accessor.cpp | 34 +- interface/common/resdb_state_accessor.h | 34 +- .../common/resdb_state_accessor_test.cpp | 34 +- interface/common/resdb_txn_accessor.cpp | 34 +- interface/common/resdb_txn_accessor.h | 34 +- interface/common/resdb_txn_accessor_test.cpp | 34 +- interface/contract/contract_client.cpp | 34 +- interface/contract/contract_client.h | 34 +- interface/kv/kv_client.cpp | 111 +- interface/kv/kv_client.h | 64 +- interface/rdbc/mock_net_channel.h | 34 +- interface/rdbc/mock_resdb_txn_accessor.h | 34 +- interface/rdbc/net_channel.cpp | 34 +- interface/rdbc/net_channel.h | 34 +- interface/rdbc/net_channel_test.cpp | 34 +- interface/rdbc/transaction_constructor.cpp | 34 +- interface/rdbc/transaction_constructor.h | 34 +- .../rdbc/transaction_constructor_test.cpp | 34 +- interface/utxo/utxo_client.cpp | 34 +- interface/utxo/utxo_client.h | 34 +- platform/common/data_comm/data_comm.h | 34 +- platform/common/data_comm/network_comm.h | 34 +- platform/common/network/mock_socket.h | 34 +- platform/common/network/network_utils.cpp | 34 +- platform/common/network/network_utils.h | 34 +- .../common/network/network_utils_test.cpp | 34 +- platform/common/network/socket.h | 34 +- platform/common/network/tcp_socket.cpp | 34 +- platform/common/network/tcp_socket.h | 34 +- platform/common/network/tcp_socket_test.cpp | 34 +- platform/common/queue/batch_queue.h | 34 +- platform/common/queue/batch_queue_test.cpp | 34 +- platform/common/queue/blocking_queue.h | 34 +- platform/common/queue/lock_free_queue.h | 36 +- .../common/queue/lock_free_queue_test.cpp | 34 +- platform/config/resdb_config.cpp | 54 +- platform/config/resdb_config.h | 52 +- platform/config/resdb_config_test.cpp | 34 +- platform/config/resdb_config_utils.cpp | 34 +- platform/config/resdb_config_utils.h | 34 +- platform/config/resdb_poc_config.cpp | 34 +- platform/config/resdb_poc_config.h | 34 +- platform/consensus/checkpoint/checkpoint.h | 34 +- .../consensus/checkpoint/mock_checkpoint.h | 34 +- .../consensus/execution/duplicate_manager.cpp | 34 +- .../consensus/execution/duplicate_manager.h | 34 +- .../execution/geo_global_executor.cpp | 34 +- .../consensus/execution/geo_global_executor.h | 34 +- .../execution/geo_global_executor_test.cpp | 34 +- .../execution/geo_transaction_executor.cpp | 34 +- .../execution/geo_transaction_executor.h | 34 +- .../geo_transaction_executor_test.cpp | 34 +- .../execution/mock_geo_global_executor.h | 34 +- platform/consensus/execution/system_info.cpp | 34 +- platform/consensus/execution/system_info.h | 34 +- .../consensus/execution/system_info_test.cpp | 34 +- .../execution/transaction_executor.cpp | 437 +- .../execution/transaction_executor.h | 85 +- .../execution/transaction_executor_test.cpp | 34 +- .../consensus/ordering/common/algorithm/BUILD | 12 + .../common/algorithm/protocol_base.cpp | 53 + .../ordering/common/algorithm/protocol_base.h | 64 + .../consensus/ordering/common/framework/BUILD | 49 + .../ordering/common/framework/consensus.cpp | 220 + .../ordering/common/framework/consensus.h | 83 + .../common/framework/performance_manager.cpp | 297 + .../common/framework/performance_manager.h | 103 + .../common/framework/response_manager.cpp | 242 + .../common/framework/response_manager.h | 85 + .../common/framework/transaction_utils.cpp | 49 + .../common/framework/transaction_utils.h | 40 +- .../ordering/common/transaction_utils.cpp | 34 +- .../ordering/common/transaction_utils.h | 34 +- .../consensus/ordering/fides/algorithm/BUILD | 42 + .../ordering/fides/algorithm/fides.cpp | 703 ++ .../ordering/fides/algorithm/fides.h | 95 + .../fides/algorithm/proposal_manager.cpp | 310 + .../fides/algorithm/proposal_manager.h | 60 + .../fides/algorithm/transaction_collector.cpp | 56 + .../fides/algorithm/transaction_collector.h | 54 +- .../ordering/fides/executor/common/BUILD | 19 + .../executor/common/contract_execute_info.h | 71 + .../ordering/fides/executor/common/utils.h | 20 +- .../fides/executor/common/x_verifier.cpp | 188 + .../fides/executor/common/x_verifier.h | 72 + .../ordering/fides/executor/eo_service/BUILD | 26 + .../contract_transaction_manager.cpp | 127 + .../eo_service/contract_transaction_manager.h | 56 + .../contract_transaction_manager_test.cpp | 270 + .../fides/executor/eo_service/test_data/BUILD | 1 + .../eo_service/test_data/contract.json | 19 + .../ordering/fides/executor/manager/BUILD | 632 ++ .../executor/manager/address_manager.cpp | 83 + .../fides/executor/manager/address_manager.h | 56 + .../executor/manager/address_manager_test.cpp | 49 +- .../executor/manager/committer_context.cpp | 67 + .../executor/manager/committer_context.h | 53 + .../manager/concurrency_controller.cpp | 17 + .../executor/manager/concurrency_controller.h | 48 + .../executor/manager/contract_committer.h | 77 + .../executor/manager/contract_deployer.cpp | 186 + .../executor/manager/contract_deployer.h | 64 + .../manager/contract_deployer_test.cpp | 124 + .../executor/manager/contract_executor.cpp | 103 + .../executor/manager/contract_executor.h | 72 + .../manager/contract_executor_test.cpp | 367 + .../executor/manager/contract_manager.cpp | 209 + .../fides/executor/manager/contract_manager.h | 95 + .../manager/contract_manager_test.cpp | 259 + .../executor/manager/contract_verifier.cpp | 53 + .../executor/manager/contract_verifier.h | 58 + .../fides/executor/manager/d_storage.cpp | 164 + .../fides/executor/manager/d_storage.h | 35 + .../fides/executor/manager/data_storage.cpp | 74 + .../fides/executor/manager/data_storage.h | 34 + .../fides/executor/manager/evm_state.h | 23 + .../fides/executor/manager/global_state.cpp | 50 + .../fides/executor/manager/global_state.h | 44 + .../fides/executor/manager/global_view.cpp | 25 + .../fides/executor/manager/global_view.h | 26 + .../executor/manager/level_d_storage.cpp | 225 + .../fides/executor/manager/level_d_storage.h | 36 + .../fides/executor/manager/leveldb.cpp | 30 + .../ordering/fides/executor/manager/leveldb.h | 26 + .../executor/manager/leveldb_d_storage.cpp | 204 + .../executor/manager/leveldb_d_storage.h | 37 + .../executor/manager/leveldb_storage.cpp | 113 + .../fides/executor/manager/leveldb_storage.h | 39 + .../fides/executor/manager/local_state.cpp | 73 + .../fides/executor/manager/local_state.h | 53 + .../fides/executor/manager/local_view.cpp | 78 + .../fides/executor/manager/local_view.h | 38 + .../executor/manager/local_view_test.cpp | 125 + .../fides/executor/manager/mock_d_storage.h | 47 + .../executor/manager/mock_data_storage.h | 46 + .../fides/executor/manager/ooo_committer.cpp | 119 + .../fides/executor/manager/ooo_committer.h | 73 + .../fides/executor/manager/ooo_controller.cpp | 35 + .../fides/executor/manager/ooo_controller.h | 17 + .../manager/sequential_cc_controller.cpp | 263 + .../manager/sequential_cc_controller.h | 71 + .../manager/sequential_cc_controller_test.cpp | 1056 +++ .../sequential_concurrency_committer.cpp | 248 + .../sequential_concurrency_committer.h | 113 + .../sequential_concurrency_committer_test.cpp | 246 + .../executor/manager/streaming_committer.cpp | 339 + .../executor/manager/streaming_committer.h | 130 + .../executor/manager/streaming_controller.cpp | 228 + .../executor/manager/streaming_controller.h | 62 + .../manager/streaming_dq_committer.cpp | 365 + .../executor/manager/streaming_dq_committer.h | 106 + .../manager/streaming_dq_committer_test.cpp | 1100 +++ .../manager/streaming_dq_controller.cpp | 242 + .../manager/streaming_dq_controller.h | 79 + .../manager/streaming_single_committer.cpp | 104 + .../manager/streaming_single_committer.h | 80 + .../fides/executor/manager/test_committer.cpp | 80 + .../fides/executor/manager/test_committer.h | 63 + .../executor/manager/test_controller.cpp | 35 + .../fides/executor/manager/test_controller.h | 19 + .../fides/executor/manager/test_data/BUILD | 1 + .../executor/manager/test_data/compile.sh | 5 + .../executor/manager/test_data/contract.json | 19 + .../fides/executor/manager/test_data/kv.json | 18 + .../fides/executor/manager/test_data/kv.sol | 59 + .../executor/manager/two_phase_committer.cpp | 168 + .../executor/manager/two_phase_committer.h | 72 + .../manager/two_phase_committer_test.cpp | 355 + .../executor/manager/two_phase_controller.cpp | 112 + .../executor/manager/two_phase_controller.h | 33 + .../manager/two_phase_controller_test.cpp | 428 + .../manager/two_phase_ooo_committer.cpp | 159 + .../manager/two_phase_ooo_committer.h | 72 + .../manager/two_phase_ooo_controller.cpp | 86 + .../manager/two_phase_ooo_controller.h | 33 + .../fides/executor/manager/v_controller.cpp | 85 + .../fides/executor/manager/v_controller.h | 31 + .../fides/executor/manager/x_committer.cpp | 205 + .../fides/executor/manager/x_committer.h | 89 + .../executor/manager/x_committer_test.cpp | 251 + .../fides/executor/manager/x_controller.cpp | 241 + .../fides/executor/manager/x_controller.h | 80 + .../fides/executor/manager/x_verifier.cpp | 188 + .../fides/executor/manager/x_verifier.h | 72 + .../executor/manager/x_verifier_test.cpp | 229 + .../fides/executor/manager/xexecutor.cpp | 287 + .../fides/executor/manager/xexecutor.h | 104 + .../ordering/fides/executor/paral_sm/BUILD | 304 + .../executor/paral_sm/address_manager.cpp | 76 + .../fides/executor/paral_sm/address_manager.h | 57 + .../paral_sm/address_manager_test.cpp | 58 + .../executor/paral_sm/committer_context.cpp | 65 + .../executor/paral_sm/committer_context.h | 54 + .../paral_sm/concurrency_controller.cpp | 17 + .../paral_sm/concurrency_controller.h | 46 + .../executor/paral_sm/contract_committer.h | 76 + .../executor/paral_sm/contract_deployer.cpp | 130 + .../executor/paral_sm/contract_deployer.h | 62 + .../paral_sm/contract_deployer_test.cpp | 124 + .../executor/paral_sm/contract_executor.cpp | 103 + .../executor/paral_sm/contract_executor.h | 68 + .../paral_sm/contract_executor_test.cpp | 367 + .../executor/paral_sm/contract_manager.cpp | 114 + .../executor/paral_sm/contract_manager.h | 78 + .../paral_sm/contract_manager_test.cpp | 259 + .../fides/executor/paral_sm/d_storage.cpp | 151 + .../fides/executor/paral_sm/d_storage.h | 36 + .../fides/executor/paral_sm/data_storage.cpp | 51 + .../fides/executor/paral_sm/data_storage.h | 33 + .../fides/executor/paral_sm/db_view.cpp | 40 + .../fides/executor/paral_sm/db_view.h | 41 + .../fides/executor/paral_sm/evm_state.h | 23 + .../fides/executor/paral_sm/executor_state.h | 55 + .../fides/executor/paral_sm/global_state.cpp | 52 + .../fides/executor/paral_sm/global_state.h | 46 + .../fides/executor/paral_sm/global_view.cpp | 27 + .../fides/executor/paral_sm/global_view.h | 28 + .../fides/executor/paral_sm/leveldb.cpp | 30 + .../fides/executor/paral_sm/leveldb.h | 26 + .../fides/executor/paral_sm/local_state.cpp | 73 + .../fides/executor/paral_sm/local_state.h | 53 + .../fides/executor/paral_sm/local_view.cpp | 50 + .../fides/executor/paral_sm/local_view.h | 38 + .../executor/paral_sm/local_view_test.cpp | 125 + .../fides/executor/paral_sm/mock_d_storage.h | 50 + .../executor/paral_sm/mock_data_storage.h | 46 + .../executor/paral_sm/mock_e_controller.h | 29 +- .../paral_sm/streaming_e_committer.cpp | 314 + .../executor/paral_sm/streaming_e_committer.h | 107 + .../paral_sm/streaming_e_committer_test.cpp | 889 ++ .../paral_sm/streaming_e_controller.cpp | 701 ++ .../paral_sm/streaming_e_controller.h | 110 + .../executor/paral_sm/test_committer.cpp | 80 + .../fides/executor/paral_sm/test_committer.h | 63 + .../executor/paral_sm/test_controller.cpp | 35 + .../fides/executor/paral_sm/test_controller.h | 19 + .../fides/executor/paral_sm/test_data/BUILD | 1 + .../executor/paral_sm/test_data/compile.sh | 5 + .../executor/paral_sm/test_data/contract.json | 19 + .../fides/executor/paral_sm/test_data/kv.json | 18 + .../fides/executor/paral_sm/test_data/kv.sol | 59 + .../ordering/fides/executor/paral_sm/utils.h | 36 + .../fides/executor/paral_sm/xcommitter.h | 107 + .../fides/executor/paral_sm/xcontroller.cpp | 605 ++ .../fides/executor/paral_sm/xcontroller.h | 76 + .../fides/executor/paral_sm/xexecutor.cpp | 289 + .../fides/executor/paral_sm/xexecutor.h | 104 + .../ordering/fides/executor/service/BUILD | 26 + .../service/contract_transaction_manager.cpp | 127 + .../service/contract_transaction_manager.h | 56 + .../contract_transaction_manager_test.cpp | 270 + .../fides/executor/service/test_data/BUILD | 1 + .../executor/service/test_data/contract.json | 19 + .../executor/x_manager/2pl_committer.cpp | 249 + .../fides/executor/x_manager/2pl_committer.h | 106 + .../executor/x_manager/2pl_controller.cpp | 297 + .../fides/executor/x_manager/2pl_controller.h | 108 + .../ordering/fides/executor/x_manager/BUILD | 576 ++ .../executor/x_manager/address_manager.cpp | 84 + .../executor/x_manager/address_manager.h | 58 + .../x_manager/address_manager_test.cpp | 58 + .../executor/x_manager/committer_context.cpp | 69 + .../executor/x_manager/committer_context.h | 56 + .../x_manager/concurrency_controller.cpp | 17 + .../x_manager/concurrency_controller.h | 53 + .../executor/x_manager/contract_committer.h | 80 + .../executor/x_manager/contract_deployer.cpp | 187 + .../executor/x_manager/contract_deployer.h | 66 + .../x_manager/contract_deployer_test.cpp | 124 + .../executor/x_manager/contract_executor.cpp | 103 + .../executor/x_manager/contract_executor.h | 74 + .../x_manager/contract_executor_test.cpp | 367 + .../executor/x_manager/contract_manager.cpp | 166 + .../executor/x_manager/contract_manager.h | 94 + .../x_manager/contract_manager_test.cpp | 259 + .../executor/x_manager/contract_verifier.cpp | 54 + .../executor/x_manager/contract_verifier.h | 61 + .../fides/executor/x_manager/d_storage.cpp | 173 + .../fides/executor/x_manager/d_storage.h | 36 + .../fides/executor/x_manager/data_storage.cpp | 81 + .../fides/executor/x_manager/data_storage.h | 34 + .../fides/executor/x_manager/db_view.cpp | 42 + .../fides/executor/x_manager/db_view.h | 42 + .../fides/executor/x_manager/dx_committer.cpp | 272 + .../fides/executor/x_manager/dx_committer.h | 107 + .../executor/x_manager/dx_controller.cpp | 1759 ++++ .../fides/executor/x_manager/dx_controller.h | 141 + .../fides/executor/x_manager/e_committer.cpp | 259 + .../fides/executor/x_manager/e_committer.h | 107 + .../executor/x_manager/e_committer_test.cpp | 283 + .../fides/executor/x_manager/e_controller.cpp | 685 ++ .../fides/executor/x_manager/e_controller.h | 112 + .../fides/executor/x_manager/evm_state.h | 23 + .../executor/x_manager/executor_state.cpp | 75 + .../fides/executor/x_manager/executor_state.h | 55 + .../fides/executor/x_manager/fx_committer.cpp | 259 + .../fides/executor/x_manager/fx_committer.h | 107 + .../executor/x_manager/fx_controller.cpp | 1835 ++++ .../executor/x_manager/fx_controller.cpp.bak2 | 1648 ++++ .../executor/x_manager/fx_controller.cpp.bak3 | 1891 ++++ .../fides/executor/x_manager/fx_controller.h | 152 + .../executor/x_manager/fx_controller.h.bak3 | 149 + .../fides/executor/x_manager/global_state.cpp | 52 + .../fides/executor/x_manager/global_state.h | 46 + .../fides/executor/x_manager/global_view.cpp | 27 + .../fides/executor/x_manager/global_view.h | 28 + .../fides/executor/x_manager/leveldb.cpp | 30 + .../fides/executor/x_manager/leveldb.h | 26 + .../executor/x_manager/leveldb_d_storage.cpp | 210 + .../executor/x_manager/leveldb_d_storage.h | 38 + .../executor/x_manager/leveldb_storage.cpp | 122 + .../executor/x_manager/leveldb_storage.h | 40 + .../fides/executor/x_manager/local_state.cpp | 75 + .../fides/executor/x_manager/local_state.h | 55 + .../fides/executor/x_manager/local_view.cpp | 59 + .../fides/executor/x_manager/local_view.h | 40 + .../executor/x_manager/local_view_test.cpp | 125 + .../fides/executor/x_manager/mock_d_storage.h | 50 + .../executor/x_manager/mock_data_storage.h | 46 + .../executor/x_manager/mock_e_controller.h | 45 + .../executor/x_manager/seq_committer.cpp | 93 + .../fides/executor/x_manager/seq_committer.h | 92 + .../executor/x_manager/seq_controller.cpp | 1758 ++++ .../fides/executor/x_manager/seq_controller.h | 141 + .../x_manager/streaming_e_committer.cpp | 324 + .../x_manager/streaming_e_committer.h | 109 + .../x_manager/streaming_e_committer_test.cpp | 843 ++ .../x_manager/streaming_e_controller.cpp | 617 ++ .../x_manager/streaming_e_controller.h | 148 + .../executor/x_manager/test_committer.cpp | 80 + .../fides/executor/x_manager/test_committer.h | 63 + .../executor/x_manager/test_controller.cpp | 35 + .../executor/x_manager/test_controller.h | 19 + .../fides/executor/x_manager/test_data/BUILD | 1 + .../executor/x_manager/test_data/compile.sh | 5 + .../x_manager/test_data/contract.json | 19 + .../executor/x_manager/test_data/kv.json | 18 + .../fides/executor/x_manager/test_data/kv.sol | 59 + .../ordering/fides/executor/x_manager/utils.h | 36 + .../fides/executor/x_manager/v_controller.cpp | 87 + .../fides/executor/x_manager/v_controller.h | 33 + .../fides/executor/x_manager/x_committer.cpp | 257 + .../fides/executor/x_manager/x_committer.h | 107 + .../fides/executor/x_manager/x_controller.cpp | 1758 ++++ .../fides/executor/x_manager/x_controller.h | 141 + .../fides/executor/x_manager/x_verifier.cpp | 213 + .../fides/executor/x_manager/x_verifier.h | 74 + .../consensus/ordering/fides/framework/BUILD | 18 + .../ordering/fides/framework/consensus.cpp | 131 + .../ordering/fides/framework/consensus.h | 55 + platform/consensus/ordering/fides/proto/BUILD | 18 + .../ordering/fides/proto/proposal.proto | 85 + .../geo_pbft/consensus_manager_geo_pbft.cpp | 34 +- .../geo_pbft/consensus_manager_geo_pbft.h | 34 +- .../ordering/geo_pbft/geo_pbft_commitment.cpp | 34 +- .../ordering/geo_pbft/geo_pbft_commitment.h | 34 +- .../geo_pbft/geo_pbft_commitment_test.cpp | 34 +- .../consensus/ordering/geo_pbft/hash_set.h | 34 +- platform/consensus/ordering/pbft/BUILD | 4 +- .../ordering/pbft/checkpoint_manager.cpp | 41 +- .../ordering/pbft/checkpoint_manager.h | 40 +- .../ordering/pbft/checkpoint_manager_test.cpp | 34 +- .../consensus/ordering/pbft/commitment.cpp | 37 +- platform/consensus/ordering/pbft/commitment.h | 34 +- .../ordering/pbft/commitment_test.cpp | 34 +- .../ordering/pbft/consensus_manager_pbft.cpp | 34 +- .../ordering/pbft/consensus_manager_pbft.h | 34 +- .../pbft/lock_free_collector_pool.cpp | 34 +- .../ordering/pbft/lock_free_collector_pool.h | 34 +- .../pbft/lock_free_collector_pool_test.cpp | 34 +- .../ordering/pbft/message_manager.cpp | 34 +- .../consensus/ordering/pbft/message_manager.h | 38 +- .../ordering/pbft/mock_checkpoint_manager.h | 34 +- .../ordering/pbft/performance_manager.cpp | 34 +- .../ordering/pbft/performance_manager.h | 34 +- .../pbft/pre_very_consensus_service_pbft.h | 34 +- platform/consensus/ordering/pbft/query.cpp | 34 +- platform/consensus/ordering/pbft/query.h | 34 +- .../consensus/ordering/pbft/query_test.cpp | 34 +- .../ordering/pbft/response_manager.cpp | 34 +- .../ordering/pbft/response_manager.h | 34 +- .../ordering/pbft/response_manager_test.cpp | 34 +- .../ordering/pbft/transaction_collector.cpp | 34 +- .../ordering/pbft/transaction_collector.h | 34 +- .../pbft/transaction_collector_test.cpp | 34 +- .../ordering/pbft/transaction_utils.cpp | 34 +- .../ordering/pbft/transaction_utils.h | 34 +- .../ordering/pbft/viewchange_manager.cpp | 34 +- .../ordering/pbft/viewchange_manager.h | 34 +- .../ordering/pbft/viewchange_manager_test.cpp | 34 +- .../consensus/ordering/poe/algorithm/BUILD | 15 + .../consensus/ordering/poe/algorithm/poe.cpp | 80 + .../consensus/ordering/poe/algorithm/poe.h | 40 + .../consensus/ordering/poe/framework/BUILD | 16 + .../ordering/poe/framework/consensus.cpp | 108 + .../ordering/poe/framework/consensus.h | 59 + .../ordering/poe/framework/consensus_test.cpp | 179 + platform/consensus/ordering/poe/proto/BUILD | 16 + .../ordering/poe/proto/proposal.proto | 28 + platform/consensus/recovery/recovery.cpp | 34 +- platform/consensus/recovery/recovery.h | 34 +- platform/consensus/recovery/recovery_test.cpp | 48 +- platform/networkstrate/async_acceptor.cpp | 36 +- platform/networkstrate/async_acceptor.h | 34 +- .../networkstrate/async_acceptor_test.cpp | 34 +- .../networkstrate/async_replica_client.cpp | 34 +- platform/networkstrate/async_replica_client.h | 34 +- .../async_replica_client_test.cpp | 34 +- platform/networkstrate/consensus_manager.cpp | 39 +- platform/networkstrate/consensus_manager.h | 35 +- .../networkstrate/consensus_manager_test.cpp | 34 +- .../networkstrate/mock_async_replica_client.h | 34 +- .../networkstrate/mock_replica_communicator.h | 34 +- .../networkstrate/mock_service_interface.h | 34 +- .../networkstrate/replica_communicator.cpp | 47 +- platform/networkstrate/replica_communicator.h | 36 +- .../replica_communicator_test.cpp | 34 +- platform/networkstrate/server_comm.h | 34 +- platform/networkstrate/service_interface.cpp | 34 +- platform/networkstrate/service_interface.h | 34 +- platform/networkstrate/service_network.cpp | 39 +- platform/networkstrate/service_network.h | 34 +- .../networkstrate/service_network_test.cpp | 34 +- platform/proto/BUILD | 18 +- platform/proto/durable.proto | 18 - platform/proto/replica_info.proto | 10 +- platform/proto/resdb.proto | 11 +- platform/rdbc/acceptor.cpp | 38 +- platform/rdbc/acceptor.h | 34 +- platform/statistic/prometheus_handler.cpp | 34 +- platform/statistic/prometheus_handler.h | 34 +- platform/statistic/set_random_data.cpp | 34 +- platform/statistic/stats.cpp | 404 +- platform/statistic/stats.h | 72 +- platform/test/resdb_test.cpp | 36 +- proto/kv/kv.proto | 33 +- scripts/deploy/README.md | 3 +- scripts/deploy/config/cassandra.config | 10 + scripts/deploy/config/fair.config | 10 + scripts/deploy/config/fides.config | 11 + .../deploy/config/kv_performance_server.conf | 15 +- .../config/kv_performance_server_128.conf | 262 + .../kv_performance_server_128_small.conf | 261 + .../config/kv_performance_server_16.conf | 37 + .../config/kv_performance_server_16_2.conf | 53 + .../config/kv_performance_server_32.conf | 69 + .../config/kv_performance_server_64.conf | 133 + .../config/kv_performance_server_8.conf | 21 + .../kv_performance_server_small_128.conf | 261 + .../kv_performance_server_small_16.conf | 37 + .../kv_performance_server_small_32.conf | 69 + .../kv_performance_server_small_64.conf | 133 + .../config/kv_performance_server_small_8.conf | 20 + .../config/kv_performance_server_test_5.conf | 10 + scripts/deploy/config/kv_server.conf | 1 - scripts/deploy/config/poe.config | 10 + scripts/deploy/config/rcc.config | 10 + scripts/deploy/config/template.config | 10 + scripts/deploy/config/tusk.config | 11 + scripts/deploy/manage_alicloud_instance.sh | 314 + .../deploy/performance/calculate_result.py | 148 +- .../performance/cassandra_performance.sh | 4 + .../deploy/performance/fair_performance.sh | 4 + .../deploy/performance/fides_performance.sh | 5 + scripts/deploy/performance/hs_performance.sh | 49 + .../deploy/performance/ooohs_performance.sh | 50 + scripts/deploy/performance/poe_performance.sh | 6 + scripts/deploy/performance/rcc_performance.sh | 6 + scripts/deploy/performance/run_performance.sh | 40 +- scripts/deploy/poe_performance.sh | 6 + scripts/deploy/script/deploy.sh | 64 +- scripts/deploy/script/env.sh | 2 + scripts/deploy/script/generate_config.sh | 27 +- scripts/deploy/script/load_config.sh | 8 +- scripts/deploy/script/prepare.sh | 20 + scripts/deploy/start_evaluation.sh | 56 + scripts/null | 0 service/contract/contract_service.cpp | 34 +- service/kv/BUILD | 10 +- service/kv/kv_service.cpp | 65 +- service/tools/config/server/server.config | 5 - .../contract/api_tools/contract_tools.cpp | 34 +- .../service_tools/start_contract_service.sh | 6 +- .../kv/api_tools/kv_client_txn_tools.cpp | 34 +- .../tools/kv/api_tools/kv_service_tools.cpp | 239 +- .../utxo/service_tools/start_utxo_service.sh | 6 +- .../wallet_tool/cpp/utxo_client_tools.cpp | 97 +- service/tools/utxo/wallet_tool/py/addr.py | 37 +- service/tools/utxo/wallet_tool/py/keys.py | 38 +- .../tools/utxo/wallet_tool/test/key_tester.py | 37 +- .../wallet_tool/test/key_tester_utils.cpp | 34 +- service/utils/server_factory.cpp | 34 +- service/utils/server_factory.h | 34 +- service/utxo/utxo_service.cpp | 34 +- tools/certificate_tools.cpp | 34 +- tools/certificate_tools_test.cpp | 34 +- tools/generate_mulregion_config.py | 17 + tools/generate_region_config.py | 49 +- tools/key_generator_tools.cpp | 34 +- tools/resdb_state_accessor_tools.cpp | 34 +- tools/resdb_txn_accessor_tools.cpp | 34 +- 610 files changed, 65621 insertions(+), 4864 deletions(-) create mode 100644 .asf.yaml create mode 100644 .bazelversion create mode 100644 Fides_extended_version.pdf create mode 100644 NOTICE create mode 100644 benchmark/protocols/fides/BUILD create mode 100644 benchmark/protocols/fides/kv_server_performance.cpp create mode 100644 benchmark/protocols/fides/kv_service_tools.cpp create mode 100644 benchmark/protocols/poe/BUILD create mode 100644 benchmark/protocols/poe/kv_server_performance.cpp create mode 100644 benchmark/protocols/poe/kv_service_tools.cpp delete mode 100644 chain/storage/README.md create mode 100644 chain/storage/kv_storage_test.cpp create mode 100644 chain/storage/leveldb.cpp create mode 100644 chain/storage/leveldb.h create mode 100644 chain/storage/memory_db.cpp create mode 100644 chain/storage/memory_db.h create mode 100644 chain/storage/proto/BUILD create mode 100644 chain/storage/proto/kv.proto create mode 100644 chain/storage/proto/leveldb_config.proto create mode 100644 chain/storage/proto/rocksdb_config.proto delete mode 100644 chain/storage/res_leveldb.cpp delete mode 100644 chain/storage/res_leveldb_test.cpp delete mode 100644 chain/storage/res_rocksdb.cpp delete mode 100644 chain/storage/res_rocksdb_test.cpp create mode 100644 chain/storage/rocksdb.cpp create mode 100644 chain/storage/rocksdb.h create mode 100644 chain/storage/setting/BUILD create mode 100644 documents/doxygen/doxygen_html_style.css create mode 100644 documents/doxygen/header create mode 100644 enclave/BUILD create mode 100644 enclave/sgx_cpp_args.h create mode 100644 enclave/sgx_cpp_u.c create mode 100644 enclave/sgx_cpp_u.h create mode 100644 enclave/sgxcode/CMakeLists.txt create mode 100644 enclave/sgxcode/Makefile create mode 100644 enclave/sgxcode/README.md create mode 100644 enclave/sgxcode/enclave/CMakeLists.txt create mode 100644 enclave/sgxcode/enclave/Makefile create mode 100644 enclave/sgxcode/enclave/common/dispatcher.cpp create mode 100644 enclave/sgxcode/enclave/common/dispatcher.h create mode 100644 enclave/sgxcode/enclave/common/ecalls.cpp create mode 100644 enclave/sgxcode/enclave/common/file-encryptor.conf create mode 100644 enclave/sgxcode/enclave/common/random.h create mode 100644 enclave/sgxcode/enclave/common/trace.h create mode 100644 enclave/sgxcode/enclave/decryptor_src/decryptor copy.cpp create mode 100644 enclave/sgxcode/enclave/decryptor_src/decryptor.cpp create mode 100644 enclave/sgxcode/enclave/random_src/random.cpp create mode 100644 enclave/sgxcode/enclave/sgx_cpp.edl create mode 100644 enclave/sgxcode/enclave/simulated_counter_src/simulated_counter.cpp create mode 100644 enclave/sgxcode/host/CMakeLists.txt create mode 100644 enclave/sgxcode/host/Makefile create mode 100644 enclave/sgxcode/host/host.cpp create mode 100755 entrypoint.sh create mode 100644 img/apache-incubator.png create mode 100644 img/apache-resdb.png create mode 100644 img/resdb-v2.png create mode 100644 img/resdb.png create mode 100644 platform/consensus/ordering/common/algorithm/BUILD create mode 100644 platform/consensus/ordering/common/algorithm/protocol_base.cpp create mode 100644 platform/consensus/ordering/common/algorithm/protocol_base.h create mode 100644 platform/consensus/ordering/common/framework/BUILD create mode 100644 platform/consensus/ordering/common/framework/consensus.cpp create mode 100644 platform/consensus/ordering/common/framework/consensus.h create mode 100644 platform/consensus/ordering/common/framework/performance_manager.cpp create mode 100644 platform/consensus/ordering/common/framework/performance_manager.h create mode 100644 platform/consensus/ordering/common/framework/response_manager.cpp create mode 100644 platform/consensus/ordering/common/framework/response_manager.h create mode 100644 platform/consensus/ordering/common/framework/transaction_utils.cpp rename chain/storage/res_rocksdb.h => platform/consensus/ordering/common/framework/transaction_utils.h (57%) create mode 100644 platform/consensus/ordering/fides/algorithm/BUILD create mode 100644 platform/consensus/ordering/fides/algorithm/fides.cpp create mode 100644 platform/consensus/ordering/fides/algorithm/fides.h create mode 100644 platform/consensus/ordering/fides/algorithm/proposal_manager.cpp create mode 100644 platform/consensus/ordering/fides/algorithm/proposal_manager.h create mode 100644 platform/consensus/ordering/fides/algorithm/transaction_collector.cpp rename chain/storage/res_leveldb.h => platform/consensus/ordering/fides/algorithm/transaction_collector.h (55%) create mode 100644 platform/consensus/ordering/fides/executor/common/BUILD create mode 100644 platform/consensus/ordering/fides/executor/common/contract_execute_info.h rename chain/storage/txn_memory_db.h => platform/consensus/ordering/fides/executor/common/utils.h (76%) create mode 100644 platform/consensus/ordering/fides/executor/common/x_verifier.cpp create mode 100644 platform/consensus/ordering/fides/executor/common/x_verifier.h create mode 100644 platform/consensus/ordering/fides/executor/eo_service/BUILD create mode 100644 platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.h create mode 100644 platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/eo_service/test_data/BUILD create mode 100644 platform/consensus/ordering/fides/executor/eo_service/test_data/contract.json create mode 100644 platform/consensus/ordering/fides/executor/manager/BUILD create mode 100644 platform/consensus/ordering/fides/executor/manager/address_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/address_manager.h rename chain/storage/txn_memory_db_test.cpp => platform/consensus/ordering/fides/executor/manager/address_manager_test.cpp (60%) create mode 100644 platform/consensus/ordering/fides/executor/manager/committer_context.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/committer_context.h create mode 100644 platform/consensus/ordering/fides/executor/manager/concurrency_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/concurrency_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_deployer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_deployer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_deployer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_executor.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_executor.h create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_executor_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_manager.h create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_verifier.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/contract_verifier.h create mode 100644 platform/consensus/ordering/fides/executor/manager/d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/data_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/data_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/evm_state.h create mode 100644 platform/consensus/ordering/fides/executor/manager/global_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/global_state.h create mode 100644 platform/consensus/ordering/fides/executor/manager/global_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/global_view.h create mode 100644 platform/consensus/ordering/fides/executor/manager/level_d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/level_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb.h create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb_d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/leveldb_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/local_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/local_state.h create mode 100644 platform/consensus/ordering/fides/executor/manager/local_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/local_view.h create mode 100644 platform/consensus/ordering/fides/executor/manager/local_view_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/mock_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/mock_data_storage.h create mode 100644 platform/consensus/ordering/fides/executor/manager/ooo_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/ooo_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/ooo_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/ooo_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_cc_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_cc_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_cc_controller_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_dq_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_dq_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_dq_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_single_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/streaming_single_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/test_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/test_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/test_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/test_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/test_data/BUILD create mode 100644 platform/consensus/ordering/fides/executor/manager/test_data/compile.sh create mode 100644 platform/consensus/ordering/fides/executor/manager/test_data/contract.json create mode 100644 platform/consensus/ordering/fides/executor/manager/test_data/kv.json create mode 100644 platform/consensus/ordering/fides/executor/manager/test_data/kv.sol create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_controller_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/v_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/v_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/x_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/x_committer.h create mode 100644 platform/consensus/ordering/fides/executor/manager/x_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/x_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/x_controller.h create mode 100644 platform/consensus/ordering/fides/executor/manager/x_verifier.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/x_verifier.h create mode 100644 platform/consensus/ordering/fides/executor/manager/x_verifier_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/xexecutor.cpp create mode 100644 platform/consensus/ordering/fides/executor/manager/xexecutor.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/BUILD create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/address_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/address_manager.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/address_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/committer_context.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/committer_context.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_committer.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_deployer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_executor.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_executor.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_executor_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_manager.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/contract_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/data_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/data_storage.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/db_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/db_view.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/evm_state.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/executor_state.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/global_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/global_state.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/global_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/global_view.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/leveldb.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/leveldb.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/local_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/local_state.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/local_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/local_view.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/local_view_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/mock_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/mock_data_storage.h rename chain/storage/txn_memory_db.cpp => platform/consensus/ordering/fides/executor/paral_sm/mock_e_controller.h (69%) create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/streaming_e_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/streaming_e_controller.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_committer.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_controller.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_data/BUILD create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_data/compile.sh create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_data/contract.json create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.json create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.sol create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/utils.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/xcommitter.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/xcontroller.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/xcontroller.h create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/xexecutor.cpp create mode 100644 platform/consensus/ordering/fides/executor/paral_sm/xexecutor.h create mode 100644 platform/consensus/ordering/fides/executor/service/BUILD create mode 100644 platform/consensus/ordering/fides/executor/service/contract_transaction_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/service/contract_transaction_manager.h create mode 100644 platform/consensus/ordering/fides/executor/service/contract_transaction_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/service/test_data/BUILD create mode 100644 platform/consensus/ordering/fides/executor/service/test_data/contract.json create mode 100644 platform/consensus/ordering/fides/executor/x_manager/2pl_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/2pl_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/2pl_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/2pl_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/BUILD create mode 100644 platform/consensus/ordering/fides/executor/x_manager/address_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/address_manager.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/address_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/committer_context.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/committer_context.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_deployer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_deployer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_deployer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_executor.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_executor.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_executor_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_manager.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_manager.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_manager_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_verifier.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/contract_verifier.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/data_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/data_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/db_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/db_view.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/dx_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/dx_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/dx_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/dx_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/e_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/e_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/e_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/e_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/e_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/evm_state.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/executor_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/executor_state.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak2 create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak3 create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/fx_controller.h.bak3 create mode 100644 platform/consensus/ordering/fides/executor/x_manager/global_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/global_state.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/global_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/global_view.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb_d_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/local_state.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/local_state.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/local_view.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/local_view.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/local_view_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/mock_d_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/mock_data_storage.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/mock_e_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/seq_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/seq_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/seq_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/seq_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer_test.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/streaming_e_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/streaming_e_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_data/BUILD create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_data/compile.sh create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_data/contract.json create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_data/kv.json create mode 100644 platform/consensus/ordering/fides/executor/x_manager/test_data/kv.sol create mode 100644 platform/consensus/ordering/fides/executor/x_manager/utils.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/v_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/v_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_committer.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_committer.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_controller.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_controller.h create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_verifier.cpp create mode 100644 platform/consensus/ordering/fides/executor/x_manager/x_verifier.h create mode 100644 platform/consensus/ordering/fides/framework/BUILD create mode 100644 platform/consensus/ordering/fides/framework/consensus.cpp create mode 100644 platform/consensus/ordering/fides/framework/consensus.h create mode 100644 platform/consensus/ordering/fides/proto/BUILD create mode 100644 platform/consensus/ordering/fides/proto/proposal.proto create mode 100644 platform/consensus/ordering/poe/algorithm/BUILD create mode 100644 platform/consensus/ordering/poe/algorithm/poe.cpp create mode 100644 platform/consensus/ordering/poe/algorithm/poe.h create mode 100644 platform/consensus/ordering/poe/framework/BUILD create mode 100644 platform/consensus/ordering/poe/framework/consensus.cpp create mode 100644 platform/consensus/ordering/poe/framework/consensus.h create mode 100644 platform/consensus/ordering/poe/framework/consensus_test.cpp create mode 100644 platform/consensus/ordering/poe/proto/BUILD create mode 100644 platform/consensus/ordering/poe/proto/proposal.proto delete mode 100644 platform/proto/durable.proto create mode 100644 scripts/deploy/config/cassandra.config create mode 100644 scripts/deploy/config/fair.config create mode 100644 scripts/deploy/config/fides.config create mode 100644 scripts/deploy/config/kv_performance_server_128.conf create mode 100644 scripts/deploy/config/kv_performance_server_128_small.conf create mode 100644 scripts/deploy/config/kv_performance_server_16.conf create mode 100644 scripts/deploy/config/kv_performance_server_16_2.conf create mode 100644 scripts/deploy/config/kv_performance_server_32.conf create mode 100644 scripts/deploy/config/kv_performance_server_64.conf create mode 100644 scripts/deploy/config/kv_performance_server_8.conf create mode 100644 scripts/deploy/config/kv_performance_server_small_128.conf create mode 100644 scripts/deploy/config/kv_performance_server_small_16.conf create mode 100644 scripts/deploy/config/kv_performance_server_small_32.conf create mode 100644 scripts/deploy/config/kv_performance_server_small_64.conf create mode 100644 scripts/deploy/config/kv_performance_server_small_8.conf create mode 100644 scripts/deploy/config/kv_performance_server_test_5.conf create mode 100644 scripts/deploy/config/poe.config create mode 100644 scripts/deploy/config/rcc.config create mode 100644 scripts/deploy/config/template.config create mode 100644 scripts/deploy/config/tusk.config create mode 100755 scripts/deploy/manage_alicloud_instance.sh create mode 100755 scripts/deploy/performance/cassandra_performance.sh create mode 100755 scripts/deploy/performance/fair_performance.sh create mode 100755 scripts/deploy/performance/fides_performance.sh create mode 100755 scripts/deploy/performance/hs_performance.sh create mode 100755 scripts/deploy/performance/ooohs_performance.sh create mode 100755 scripts/deploy/performance/poe_performance.sh create mode 100755 scripts/deploy/performance/rcc_performance.sh create mode 100755 scripts/deploy/poe_performance.sh create mode 100755 scripts/deploy/script/prepare.sh create mode 100755 scripts/deploy/start_evaluation.sh create mode 100644 scripts/null diff --git a/.asf.yaml b/.asf.yaml new file mode 100644 index 000000000..cfb2ed0e8 --- /dev/null +++ b/.asf.yaml @@ -0,0 +1,40 @@ +# Licensed to the Apache Software Foundation (ASF) under one or more +# contributor license agreements. See the NOTICE file distributed with +# this work for additional information regarding copyright ownership. +# The ASF licenses this file to You under the Apache License, Version 2.0 +# (the "License"); you may not use this file except in compliance with +# the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +github: + description: Global-Scale Sustainable Blockchain Fabric + #homepage: resilientdb.apache.org + labels: + - crypto + - smart-contracts + - blockchain + - solidity + - distributed-database + - key-value-database + - distributed-ledger + - blockchain-platform + - utxo + enabled_merge_buttons: + squash: true + merge: false + rebase: false + protected_branches: + master: + +notifications: + commits: commits@resilientdb.apache.org + issues: commits@resilientdb.apache.org + pullrequests: commits@resilientdb.apache.org diff --git a/.bazelversion b/.bazelversion new file mode 100644 index 000000000..91e4a9f26 --- /dev/null +++ b/.bazelversion @@ -0,0 +1 @@ +6.3.2 diff --git a/.github/workflows/build-push.yml b/.github/workflows/build-push.yml index 02701aaec..0a1c92394 100644 --- a/.github/workflows/build-push.yml +++ b/.github/workflows/build-push.yml @@ -30,6 +30,7 @@ jobs: with: file: ./Docker/Dockerfile context: . + platforms: linux/amd64 push: true tags: expolab/resdb:amd64 @@ -39,5 +40,6 @@ jobs: with: file: ./Docker/Dockerfile_mac context: . + platforms: linux/arm64 push: true - tags: expolab/resdb:arm64 \ No newline at end of file + tags: expolab/resdb:arm64 diff --git a/.gitignore b/.gitignore index 7e0ede86e..96d004d83 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,6 @@ deps/*/ +scripts/deploy/config/key.conf +scripts/deploy/config_out/ deploy/kv_server/output/ .idea/ .vscode/ @@ -9,3 +11,6 @@ bazel-* venv sdk_validator/venv __pycache__ +build +enclave/sgxcode/test + diff --git a/CHANGELOG.md b/CHANGELOG.md index 01100cbdc..b470773df 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Change Log +### NexRes v1.9.0 ([2023-11-29](https://github.com/resilientdb/resilientdb/releases/tag/nexres-v1.9.0)) + +Support Multi-version Key-Value Interface. ([Junchao Chen](https://github.com/cjcchen)) + +* Get and Set need to provide a version number to fetch the correct version of the data (if exists) or write to the correct version of data (if not overwritten already), respectively. +* Provide interfaces to obtain historical data with a specific version or a range of versions. + + ### NexRes v1.8.0 ([2023-08-21](https://github.com/resilientdb/resilientdb/releases/tag/nexres-v1.8.0)) **Implemented Enhancements:** The view-change recovery protocol was extensively expanded to support the following Byzantine failures through primary/leader replacement and replica recovery. ([Dakai Kang](https://github.com/DakaiKang)) diff --git a/Docker/Dockerfile b/Docker/Dockerfile index 5b45b3f26..31571586e 100644 --- a/Docker/Dockerfile +++ b/Docker/Dockerfile @@ -33,3 +33,5 @@ COPY . /app RUN bazel --version RUN bazel build @com_github_bazelbuild_buildtools//buildifier:buildifier RUN bazel build service/tools/kv/api_tools/kv_service_tools + +ENTRYPOINT ["./entrypoint.sh"] \ No newline at end of file diff --git a/Docker/Dockerfile_mac b/Docker/Dockerfile_mac index eb47e0ce9..211976d87 100644 --- a/Docker/Dockerfile_mac +++ b/Docker/Dockerfile_mac @@ -30,3 +30,5 @@ COPY . /app RUN bazel --version RUN bazel build @com_github_bazelbuild_buildtools//buildifier:buildifier RUN bazel build service/tools/kv/api_tools/kv_service_tools + +ENTRYPOINT ["./entrypoint.sh"] diff --git a/Fides_extended_version.pdf b/Fides_extended_version.pdf new file mode 100644 index 0000000000000000000000000000000000000000..4d7126a2df96202ecf16059d68791fc7b2d4eb50 GIT binary patch literal 1062036 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%0Dj>i4Y?{Z)>Y3M*vA>e4%xF7wd|*e%$_Y&Fd5| z{RWuoiJ$Q?VJw)0S)koPSG5V$_The;5*XwIzMv^9bJk8k@c>c(dv7#}46DfcNaw zup{y?A}2QTST9+=oLVQj;Pd}@N*};upw&AJ8LDG8!f8V74BaLtgyNll0SGvni@d-% z3c+=t=jJ@0teuLvt~-k~K>p$*!Z^sGf{MAM~w)!Z{;xlL+vDrP!c6AH0+aoke zFUDb%-MwFJTP@@R*RIwImbT({K6nb;l;Ffr^qb~m6;tARsOC8kA2pD!9{DR}W13K= zbqF_?IvL~pP^hKuSAd99#VZQO;T+1?ij{v~E!4*X%Z7m>4mRlT~ zq`6-Cf!lM$2{5QoeHlk#wIsKq7ixqWFb$+@ukR2Q+794H-cWA-p2d=Du{F232=dV; zm1g$huorJi?e+Q~g~JH_%S>n0Wg<}W*pH?lu{@wnz~dI{TfkwewHo*UO-< z0nD%O%B&SQ!YVzlUx0K#F6DN}Kcv@%5X{Dgk&Q{e>p2d8&D$PA79ezAfo1yLD%JkeyiBsBNA6Ny>^IGo)+b&cjwG` zaH?2t@Od!n#b32bJ$wbED&1(28>NWPm`k?pS=q_~RZbnU#2NOY5FeDFA$I6#0%c-r z?C9iRVqo*1$j;Cbij|oepC11|5jQtJov4MilZnH>yS0IniLi;0ov{f%owSLqnUgs_ zI~yB5FE7;pNO#LhR*{L_WJT$^RI3XGWd$EDGWG@y0aR@81Kl7Vqm9^s(kfu1v>uN; z9{l-q*zw!}5P=`>A33wwIh>L&!1ek3z~1!Xd}IGETW0gkwskx`{D}P~@o+OQxp8?p zd|3b7d426%mAkm)cAoW7h<#RiWQLmz<@dqtT&#X|UHMJS^2Hg!L#Zmn)%()Bza2aa zIcJV)eTs`tLy!Eb5bAa{nbeEe&Jb3dt{_8o%^ks!ogvU?=Ihn@_k8-jUb$Qw z^0M>u;BrryZ#8J!QU1C*+4K15_r8 z1Ct>m2&z;VmmlXy02@36P?+c3moE>N)vQe}L;zlxH_i!O2vk@hPY;UDqH5tL+BaVU zSh$}87KM{Or@ZDrZ)iSALle?sZec~6S8OA{hXFYFvu$Q_)6;qTsX+*JK(CH9U<(U# z$XV{Vmt-G^@RY%hHhC6x32Q&I2J~l33m{dS%xAcprtX+CVI^jjM0VO4e9L|%%|h`; zWV;SbEUi&GK5##sVudBfHGwszP(tg=e!EL=u_Zm*djFf34-~SCY>Z5UtZ5hY(*H!8 zDda@Z1)C!&sTA@a)Eb>=meC+8-DCoNV}j)nhyK`N%r2d25p3Lg&GD zVh(cKCzoj%r12R^nf9R<7t1RcshFm(@H}E)61&l zjQYQ8Y*BJ%C_Za{TXJU3j>PP&DS@d=YaF9pV?6yt>c0;;{mXDpV0x2v0Na(AnL8vl zyGZ_*ATcwPnia11uqmfLgXQ)&fv<~w9B(#1@ymj=7ca&1p>y9n<_KPP(8^>iA2n-2 zLWm4HM%znu2ma%Rb#@<%e;1dt_pbwdq33!nb>agXDWu8-o}|;f+I-Z|Bfxo z|0!E`#{WNTw}7+?T7j&`lm9QaK>x+|?mukB{}0(#{(rIcrC1Z=>3O^TU)btOW8=N3 z;pM_jz7dV5+~E7&w? zen7dxzfKD6aUp_+ehPD(d-CMKGn%wX`Eh{?bH>?$3jPWz+Qvj5aaKI- zqu2){IAi%s-7=4{inW(j4@%qC3P9T~`xW7-r9bIRT#a)pm6dS`*S259uvoMe*62(HNhy2nHk!%3>u2$%lEQrsz{S=75pe;PZ9XGneP4(d&N<$vTYm{CBf^T;muXSx3^{|8R4Sr@tjI z-AnNTLQJLu)5$)B{vmrmbG#Kh2dxlP_Rp1Y;t&*lL#b2 z$4h%v>g}9%e8Znp3!8DwIvPCN+|8w(WF>)b=^$jd;1qA%Rv&;HF*;RJ8 z|2N0V8Cohi+5F#gEfYO6`~S;qEQ`;<`kzM*j!yXOOsxM8+t->>vDmGMJ*R4SY1-_> zl@aiL2!vYkNPXag4+QXM_!P)uVNI1)V*Zb}kKV)S-cvJ{o~<}sq2hb2ouy;6E008- zmemBonp$OVn?kg0QTiN9DFc0nDMG6K%|Tn>HAoSDZ5HI&#%M)O+ExfpR#pPgrWQQp zr8Zfxhn`TT=(7xvhg{}Svm{L*XdNLXdADdA^4eBsP#Zgfw`q7b5GUa99P#n-f-^_P zeuGgK7XXnW^s8}k#f^moS_JjM(yA9_>LGj<`F@z$F|x;rh?3(cS4oOYoETOzu-O4I z@k%wgK{N*M6{t8uW_P8S5f*~rOtU5iC%Wternm^QlN91J=s8S9VD4-X+RTka0x%oq z8U$lgaKxKbq(jl9UF{}gXqN)u1E}_^^r;7W<|Lra{jzWyAjpD{DPsg^7LW}VBIQs& z#%J<-#bAX)niglwNU!<gd4(O_(7nycTC~~+H&?eQrHffVXYjYfr3Y5;g{DYrr}U_+M+bg zLqLNX9aBq&6{V<70oopsV7uDlHAJJ@XtObIIFmRl4iV{;@%i2a99xVoI?}ZNTKG3wn{nYicjCFy>E7txzM0qdo}QX@@_(GB3xH6V^UgFS_u#!^ zj{_-?;@xonm6;%utI^KgAEfAMXZQVd=zOfZyK-OiYW@D(Dhx@9=+mZdRxDCJNke!V zMj&=1H;DaoS*qvzyeVgP`}1yr<@hPH)&-OK=X5vw6R4NF_wDip2$}5n7iwWxhIi|W z*ZK#6b=P*+VOZ13njdbgvA&O{xt|Bo!4WH-1MrECuY3Lb^N`iL$H(;rUVQXZZgytI zHTSrpc3%>F3lJiIPz3aSkhtD{dLT-*&V)n+2xJeu?iindkju1M0e}qc@3Z|au!`LI!YA)xr1Uj^@F!P6 z+uwg(b4&S2LDD|C0wDC`zj2Os?ou{!yWMF)EDrB@36{`9UlCuPozfZsKVH03+?|Nq zayloa6%`SqTq0uoB%Kv8PF85uIZh> zLLF0b65c!wcLDT?5{_~e9B#hW?s|v02dkL~C=#7jFRs@w1|&Knqf()Jq}A4%f#;Vv zz>{LOyI!jEX!lfz0)2{&4yLHyDQk*W@K4~+1xwcz0)0`^uOq79VHWS&@p-v*ONxe( z_6@oDUiQ`QmCCEFr8$F)JjiG3Bqmft&LV)d_Jt~^0GdxfUQZ~SS~d8f1!6r@)ud6! zajiHOTeSh1z7zuGH-jG`5?jl)uB}$nKd@XB8go9U?)|E8uS=b_hYa!JS}HG&t=w&2s{h?2L>X}mSI3PCuc5M$Ax01`0=%kP zZu49M2?DWNHH|MtjWw;PHGtw$;(z`-jsi30;6tpW^tWXq4k3+ljUgBi6&F!-dP|cM zU=(V2nMHd2j=L1%c;Zot{|08ijD9&Pm#5fVum{y&0FVMP`Zx%IEdZbSxr3hc`!>+( z_>=Zj8>ZPVz$)kQ*~@KL4yd^{7knGOSuV@dz>#XQ{6235e}P;)wAA$PHS& z{V5KHgZ<&ojiCJ)X%gp@i);+qK}>FxD~_mD<8Xw8s09H-m2^C6ndqorIaw>Wo{(y+ zVh?EUzcF@BF``Ayx^3IqZQHhO+qP}nyKURO+qP}nwsrgDnqxfQ>hFEqiEqkH3Rc@!!3eXg=>wi(Aq?4;cWVzZTFOJ~P{Wl8#Chd8 z1V1o5(E4U)8l1}k?tT5cc7DZ!GyFx2vG?mM`oh@5d|KidxP|GC?_6|TNgsi=1d_33R__B_9xF5&G+vMN7KV8#7uqWQO6s}nz4Qk z3KS!OJTw5#a9xRjPY7>qBpxe}wLWrC_B++>bOn$u=xSHepp$}cjNYR7OlUQAPv?gU zL1rumRZr!@9<@hyFe)xvlLnUnuK{;L8`g;WVHGW-#Tb>jMvML?u1f_meB$ju zS8cgqGf+#u(SxUsEP(u{@@UrSzkP5*%o0qKGFc^ZvC;ku;z=o3{>&`A5G9Cc?L01t zp^;YIe6!;3TU-(2!b|GnpO%oAdqW844WSg3Wtq_=BAfmz!Dm>{?rr69Ym4Un7wRAM z?ZI>F)haKh^ecDVcL1#IzTwLWuuoR4coJ9b6j*c+I;PHBllY$P*K*N3eu{3Efg?=WkQv@nMB)^R@JtRZy&`t znn6?JaTULSmFKL6^$B2IcC!ORl0Z_h=)-f%*zh58$_ z-IX=@73boXqt`Lwpz3`W1S@Af6q;HxC+A6{8#OG+W+a2-L}|oeb=N6d?cTL>x^!`= zKH2v`E)aV(oUwxSmh|TTWIEZ9OqMrbk>nCI8MbNxH8DczR2UljC0*>(Kq_xBfUcAV zokm3u)@*Ar9R*LtEL-S8pNp3He9g_M=1C!aIvNw| z;)#3z&iMXzD3alhh(!CIm^Y&%PWOIeI8eQTkPb)xXAr z358rCV;wni8wLVD6^tCq3dyIM-U9#WQcGot0@qMK7YViiLICFhk&i#iTX1g1z`p_F zNtq0Yf+MZqgcW^$M4;q*h0xeAkuzP;h z+~o6mh>d*#Njir}7N6&%1V&BU1_K2$K#gB=xn&B0oe`u4K5H~Sha>-QVtM}7o+ltw z<|pkaDbXzObMh;lKpV|n-T}QLkxS5GgESxcAu^l97hmreiG>c_z5p3YW zBo`aWp$MZN@+BnyoDV?sNc4ZZQLz1w8^!;liN}D?NYBRbpKAsqJ_92QGsk~Qrr$IE z)9hnlVrBTh+kM3?pz_)q&2>>;_A8=6UI*6*_~Q09c>%*)*}DRQ_HGc@eLSrlQnUDd zK?AuyuFqp_C*40PU7jK;3t!b7$FIZaPSs$VlBh0d4oqIibT_6 zQvjDbVE-WC-agviF}mIu<66gf5IhY{PdtXfuFO$WJ|op8JP?pCo;s zJ&Hd*F+o8`snolAfjysF1hgssJ?+FsH^zWqvs&$~>=1SJUna)ie+<5!`fsUJ(X7E; z-_?R6PW-BVsF1nvxJG`2@)|fQaaKdF)aa`lv(t;Zs%ciIhcH5daP+|#TmaMjY4Bai zt^I7CrWXKVb^+1?rhdLZtpH>Z;QeublUq&zt3U5=FI+)-+osR1tT}!uR6*8#0IaYW z4jMKNB|o?iJ!%r-t6l(|8m+AyfYe#q-aVwxR@R-~+yFa2ZG}(}_Z#6~a&?{AZ9Kbp zPE2ZHewSO{yo3PnJ7U3rx4#C$X19Q}@ALB?m&Nx>e%A#(I_w_~dmmT0>7N#kAISF} z*^eG!^B+F!<{vzXrGSF|X z4$Rg5C9uQCo@a%hnSifssmyGJdc*HY14JILK!%zz#ee?Lo*?`x1- zIf33Cq+Y(~|9*1+h<{>W&hR1mLG8bZ?a$1*fB%ht5At>UuXl5Hbh-U358rqQa`;&8 zKUI(3l!#{RRQ{{@{w z`p5ddUv9rgXw1*g4<9eEj`iYB^hX;FNw#dK>R1>+>DFaFNC7UQg1s(h$q49n2rmh`U8yd`Q7GeeCR*g)Qb8O&Q?}NZf zJO&s{y$Ix%F~tqgKSEn02KnPz9isaw(&H${;?Wn|bUuCmJp>NTOIEXv2JbgM{*!{E-{f>gmxYMQ$uN1y%dewB|+KSWw}<^vjY9y5ejj<^3^*G z^RSnaM%)++8Gr$S-`h6{mBYMq$WDOlx`7=F^{M0?;>lua6`zYVmL6GMhC?ZMC-Y$~ z!LH$B;h8FP?h~xEk@YjIcdP!XHv~hOTTU@5Ul~qgW@TCMgn-k4$%dW}T9@TztON2` z-{RB%YycnvNo^lTCZwi+C`dZ^Owa=x2Jzj^(QbkYx`8Q9%Wt;{x6ny7XLn?^CMUMl z{K24xMrVjI^9}}=V2q6$Mk*N7x0ZdHq%6{KUC02MQL)-1rFnTA4r~CdE8}hh!LIA~c&^{d_szO(fAC9fCsF5RS^R5SCX)+67k5SrITHJOrx* zxrRT@jVdfxIIBpxoLZG8U;tPKv6C{QnGzndzh1UVm2JAJ(HRVq-3PR=J5W~AYhHto zf@aiZi(=)7?q`_=`mBW7*J+*cyveVeZc7xHsQAWWc%!AVO_i={Foeme`8iAt1WHWoo_3y1H)(b^{FULWQL0H_vWpw&gq%;rr7SODbxnO z@Xo(p*I~6n^(DEtXSm=dnCluLrm6K2syn6Ia!bC#%uhiC>t#S zRIN!@-Ko(!UhuMQmX;PGR_3XF=`=1)3nMI}wjQ%6(>{4^gYc3Xr5^EyByvr9$^--S$!`lKulLK}5!@pkEd$;7FLwd3FF>VH0bt0rx{Q!4&V! zF{$22Y?w|^jRj4~{heI%0@Lj|SB)=L#V-E;*zRz>EO}DN84U3l-FB{<5-MT&oNDa* zS}aau<9T*-Tc26IZ@-lx9$lE*&HQXJdX-El#!Ig~#mAKS6lbsIMCNNemPj}RcuYrh zumBp)Pv(U%%HKdhKe%kinoyalxFA7b)C>Jp#?b9c?UXg?uz8uO9Ybwox2H{ zs?PEf^CU^5zY8pA6y$jfRgE;NJ_2^baMV(GOHWea?XC;Tv|tKVb{Ok!NSihm$4}4| zOsW{YqQ<>5Kq84|^itmX-^-2BQM`S{sw-VwvvCa9*86&0Beop@e`INwPG5~E8QA7E z_%suSdM~ocY%?(#t%2OAL&h3PzPJ+lO=dzEFH8+lv_4Eb%4d%70MI?!O2;~2xq#2% z)h1EKmhBlu&|9S(iqtS%f~+{6*Qut?WR(*rg}`V*=HsKN665xfYo3p4Q^kPIDk`;7 zN$ovOtlU{$)9hK!hD!$a|0N%!-Fhg^;BBcG^CMahcZ3XUZ?0P@`klN-@(6&FOTRq);U7V8`mx_FsGi3{>fde9o2{zm@z+Q$)-1a zW_x|hMG)(8VJjWkp3V*$!=Z&$jMqx5_OMwy9lZ(?k+Uy1dBxut4gDNbM-g`WLoMI@TVH@ipSX|Ge z=57)UZV!;mpxu-jjoBjk=i8t2SP51k-0Z?|7&Rt68MU`C&TSZSldPYBjmubpE-!Tw z0^EnKVM9&#M&M))(m8OEoKDTjL-$UJQE<)Nc5tH6?k9|qR$m)3HeUk9@Xnx5IT)2I z5?&cC{LNClKq!r~nK(ncHCrMbVu;(d(@pEIK%#izMWoJ94a%3|QToPM{9*vei6El; z_idOap47xU=Phi-nYyKIu2+Ed7Y{&31AbtF_o)kuu4w!+`2N+UrSKUksV1nK)GmQu zO_RwpjXo8oGoh+Kh<$UDmSMg@*~`zcCM8a(#LaCXNA>ToFPPT>1o)40B7kwYSwWHQ z$3%YBl#F~%224rA;fyQ-X4yhn)lLgQ=Kj9?x+B zyl^|QTSUGIhwr8_L30Z9!$M`OoqSJ@e;vtr7$R`UVnB*#no#k56^C23}0n?P7o)z z7>o_gPIlR|&-4}AG=${%_^@=u`3nhB7Q(SZDfP>6+*xG-^+HF0%#8!hXWzT#Uk!>} zUiW+#4!U^X6qCZ;&yIzW64{V=Y!`@thzEZIpB zVzgk`uA6LM0&9KTY2h-t+Z@dt6l;a+8R|WEIlAV<^tlfSoYWAusn=|p0j zvsQ0B`w8XFX41x#bO{EGITw{BHS|v4_P9;-3|t86Hq46@vc5`0CaAd)Y3w7-Ej8++ z%nWDdI<~f_HS@J!Exh&XAhZiH_)t#&Hdjj>d9+wUa6E)gDmjdfBgO_mH_pUpa?iEB zT)Q7*GYfVOg2!GeIuY7UF-O`ub<&g$nzr%THA>ZWIv zO_?ab+!VJPbzTt{+(sBg0LLi|NBnS@pCa)M&*@`&hzn)Pn!coj;*RKLPYr!;z$HAb zPOjq8Wr19-lU(L?tNKle%>Wfd03ly|YxPB14egO3u?|DN{Kd6=)5@4&ngrs`r_?^U zwdM6oh=iaogOaIR9e00c8zU{z7;Zc5lvs?b(wPYl2`!b>M{k3>Q zTMOGjYa^ydO7x5MY+XDmM^*+L$Fp?LUO;S~={S}NTM8pQrp3NFn_XfNj`{QpPKWff z>x6@B+kCO$MEjDZ)Vk$cL;;`D=# zP?Zo{BzX^s_y??Ywq2-)8wv1?8Sar~*qFYSw^!#_t)}<4v_=e(2y5OXyo4>)KV4Lq zF3DRi^>6zEY`*D7N=+zz=AElA-|h@#IA96!9m!@47ePSjnrBo;AVVVQ&Q!JMq<=)01krn+ z8i&L@jC}J0vZFTD`K9X*cg%Fy640%nN`XZIP*?m+gZA0u!-meVXUBA(klJml3N~iA znTS^04c)B~s~MwbH}uYBoq`+0iyYp_F?IYJTSasy1L8C`nk^jIEVK>`zE}plUKNwu zTfLKI;Arz4)PxZ%pq&iMoo~TiZ!4&nb~YWWN@l z5ldO)w^&$XWs7vDzzwOlq%sc+C664E$KS@u+N{i5V+86SviASw{1xwq%L?NlIG|LB zhr?!Ng#(5BK`2=%Scc&vym<<7qysY44?6iieZZGCaArZFd(*ufw5|pr5hCKt4f3Pf zH_Tg^{FZP5V*+L^L;Pd>U?`~`!P~8b$xprT?9qU9#i*%Ob)`%R%J@alG=dOd5a}cj zMRP~V6Cr7eCGsPbwa|Tr9;=m$Ld$-lEH5?UJ|n3*!&-aRnmKdgJt=_r?x<*bDXLY^LKMsF*j-iE+*?yhR z<4jdDy6O-0^G*L-$mTV4da1CCascaV5c=y^O>vf`(=w& z6SNDM72LDy9etVu3i&2S&5$wXo)TG=lz4VM!$_xqxTqv!y55ieD{R6kGRSi}La-}{ zKNAf*`vi7Lpe4{R&jp9IdUpX*OKJS|By?WL=LrM3P~91^XZi*DiK?|X#8hf7V>0Dt z`Aa*Da4*rPp>oV>9Nh^nDqc{`;M=iZNqqpaC6k>z-NQEat`yj{2II}*vg7#&er(E- zw~T~oMpFngb6o!I1vPRh-#x*bZv$furi6I>4ZCRao7m;2yw$Lno~9~}b8|6(S#~xq zPFx%=ODgo7x|O{N9$Z9H%*8vUWo^1|SUR;YBa$yyT2klOZ~AcS0wULdUCqoq*7TYX zN?rO4IBGP!sr*rN+Xx|EZg82deQhahJ&F)bIxrIlwU15!06p$IjjtBRF5YJBe3oYg zO4iP%P8=FPVp9YIW&p9sB3NsW!M2-6b6;|pp-whk5?2+O{HwI=ZM3Wh5jWF~VA?9T z#dUGxS4)tKj+ZVM=@lcaEUtI0Sc~9-|F|91ew++rIKVW)-1HZ;GwzDO?r@^~AyQFF zPdUYYrG}JIYrSAN6TQXT8riY!!JeHL>UxfpLBWblW!+aVJ2lEWh$|lDR5%6DJ4poK z2cjIYCrWtf+SW;FimP+>kIS#?rR zzz^c`Uuoebl9-YA#ABTkT*_QN9L$U@2UCa~Sq~A-JfV9}Pl)?9mWt!TD4jx3Xww`%Y!=hG&1Fmn^ zby~k{s<{ET6d$!zmSl7o-r7;C8uB~0m}BKz@b*k;Yu^iUJW3^W_MZs~nZP={ArL-u z2Wd;gorg$5ht@2}umkJ1aDDS=!IeH2XhX@F+Z|RVRDr6z)aZp&xceudxMoL*EBy znHgI3Hfdhfa)y}!ushAxeT;%Fno+mzGWj2b!?^Ga({n+qpVfis8J79c#{LH7%QBp> zYc4Oei_50F-Wb;0`_?03i7M}{A`WGco@rPv-l8GY`#!EH$e{wZwQ~cmgh*ncS*W=! zs1fWAOtYb&OO+Q7@+mJZmYHre&`$% z@(aByk~;?G>5iPjTO}i8*F0SZ0q3_V{~GU4*2JU@K@O=UH<%#k@F!HCOP1iKbyxh5h9a4LNFUmDl0t%}X)RWiefiVshiSErWL z5H{>8y1nbSn$uXL{ALZSP8ofeS*AmwsD<=pP1~RzS9{KUFgCpaL77)j^DPE-vw0O~Sdiv-C+5JWk=d?05IC8pLaOhm3&9#TC}B1WcLk=DEgt8HWb%KI zBz(8~b#w7ZiE~RzZT<)hM$T9Qb{Bf{I#^3e@#782%#`)ZpNL^n-M?1FDS~EA8PO{r zeNee6(Qr!bJ?7e30TomAa?@~h`!32u&@5Gk$(}l%7QmNCN?tG61F3kE#HD%)v?twKPl0u*Uq1P z464I=(&n>PoPT4A zGE_+jAxsi!R!1@p0*52j#-azJ*-0q*AM`x{-#Xz@ z%&LP3!pX2NFU{?5XyDLvOg$Z-`MHQb3d?)Exg8Z~m*leR`_RJqIqiWd$YB7nyFdF65uD%5} zEPh^^b4NTWZMe~-MMY_#O?g?+93<9JKH={EvuQ}D31FeFcgR=76LO1YpdFjfOQH@Y z=h?EVO@%Z1(*XUnc~Bqp&MTK7_>%|Fl(4!&Kw+|Jo_&%nW=F~^{-vmPW#!$FY17!7 z>OB5D+49sUZiHN0G|J#2xTq_ApV-8RRn2LJSb{@Sa&lrLlA`4x$H$}-Ki!Q zlX|V|4}$KvA^`hgeghXn#66OGSxuVL1>Pxxlqi6VEW1672a3Ex8SxeYd|Dj*~VNyG>glRhB^P5zA9Uf%c|LaF^3F*r~=$*B_r zd(9+D>t+hn2=SaoKU^?#nCmti^Yt=!XT@nKvVR!x!dpSUrfCprVztQ~f+vGtpIxUC zU&o0Xq`t07S4hU=6k@G>dys+=G~ykQ69*7##F-Jk1@Gj|$A}^5y`{~Uw~x*_9TKx@ zlY8>!fLYN~dbrkxt_5<1-8&h#X9phqjxvOy*1kh;y8H7zsvYpf)%vrm$7zOvm)hCh zmROR8vklU@uF24`fbrjOh~?0XeSf*#pp}@%UA8E85RC_zgcm+DMrZ9w^s9q__xppuqv}^5xn=BBD}R4H!e@`BYep;W8nyVin0E{3A5h+|bM^H#0H9yAk9>@;ZhMlaWxnMAZl?@u1rXPmew~p>1lc zC~RoX+iajdJW+z-N>Zz)c$?y+)>CwIyRuT@(HpBuepY0rOH{?Hgl1rL=U}j@^~s;1 z;xbAP2nRdx8=aK;c^{>La)M5LM@>APN$nRW$ANPaF1nC>XaqJU35%}(wsjS%_-vBL zD)O%BqC%MXqx?2k%JS`+a68HKG;yC+=hE!j4_At1nOLeX!=HVOV|EwX3LNQ!EY2I2 z1IT&QBiqz?AxL?HKXrfXFLa{SyR=Z)V=5R1``l^epY>b}l|nYx#m3^{yATp)F+SSF5KU0BIQ11Euf(Qg+rU9rE>=!3sf-9nHZF(^DL8DymesTw(%&NF!PDKWy_=!9Em^)?uM z3Va9Yymov3kYG>Hu?ue84Zc`80z(E94~IcpfBE&e2Od_C>Kw#a#U zK|s0AcguM{bvq}(sj9S)C)9DFrwKMCNL+iqTU6uY0gD?kV`xivT4}Z(n9H;>SmLr2 zRW@sN!qB)`r6r4l3VFcd^yRszyx2f(pU`4S3@)wta3q28S{W&v7i!n~@vFa(XqaK) zScKXzD0h_90{NM4x~Zyrk0{n;k=ZH)m+c~;@42$)I;=>wwgf1Oz*TAPSe|ciLp$|N5R=GUJ(( zu8Flx)?@L@sROYCTk(HAwAo_V?Q%uY4AZ<7ve`N^q@5I+Yk#dSV7un^ce)C}(5O?? zoG;U1qnf{OnnQwD%#WSo5{KOAVcI7PWRy@{K7cdl>F%nXJGXPc!#7jm&lowo?VSH!EivK!n^J!$%zO6r4j6dgNrMm$1W{)8KmDqi~i8!aFQV5Sbv9 z$3@(=uyKb)1&F?@C^XEpnE|Ste6-#|&;59Vi?V{u6EpzfrV}{DJ~eLq=@HbAL}M)0 zOr|#koK$G{U|q#Yr{?>VL;>xK=X^uPQ3URj>pQ82C5Y5y#S_*yfJEwP^f-O3doT5o zq@88SYNF?BfyvX^R&MuP7R{CT#z{n>rQi$*_*?!NYCxc9->8p_Y7aZf_c01K@I#HjcnExB=S*}Y#PvxTd%MWOq3vH9-(WxH!>IjOAjsw!G z{L(93&Dnc#%((arCOfLyaI?q-2L^LO(-OHrq09v?ypM_fS9wP%zdjX#3mgr<-f;S# zb_(n>N6zh?sPEmg!di`fR#l|9A76Lk9lO;m0R?x7MNAPwxF{C2mcszfNbxfH5hRo^ z@`2B-3uX3Yragi6td87BREii^297M(99tNe71NGXsBHPmHvt)Ta6Y547B(1pUWXe5 z)0OPgGwp{VuQpZ?yl{jn_R>O$NXx^}IQ2QbG zur=?@fw~LB2m|wE*@T8sfCDVQ?1#II}grD=Tj zsGoysJg+ie_NV}r4DFLZ`R39^u0kMZ^%}W*PQ4x^ zjd=^^z@^Z`T>_}M>jTf+(U(N*KN17o_Xd*cnMhi+YE+&n)dNp-)mmLJx%l^(Q!3=w z!93IBQu5gxJ5}i#%e|?--FNMrjgy;MAmF8b8;;}2MNSZ_GkWsgTr7&eOM2`0zA>hW zE8#Ys)$>k*RVk9=WZH0NXY@LUmt_A^vnD`o4XCfwY8eTvoM_9);KuPjkU-KhPpQrJ zQJcY8K28{m*&{fA7jn;cCZ&Wgx)zDpJuU{hPdEb^)M=xH8~X{f2PKQUWv6{K(j=s> zr%j+ME?o)G2+?Bvgc$^%2&3QZ!5-`4$c=dBampyqzs~t6souJN_eE8VcH`k4sx}Np z4&MZFNKXt0F&bU67;k_{fSYl%FWVJusfiGoEC$@)BFo2C{r$0{$2={hqSF+08Z{Ul zqiwg(RX6?$vnlf3Bb3SthNkUKIua{$y(^Qwl2|*8y{5R)`HG{QFF}OJ^SmWgI-mo3 zCB)}2vG+%;LVQ%ot{Ap_{ zoSs3-Mj|fF!QYfjy5!WmHQwAo(zsc-yAdI?vg|9W9cm0FOnz8(GQLQLY*W6jJkdQ? z(8)cWTPqDEQdIl5I(6(Acoqa13}lb4irsCdx+S&?cYW>;tRFC8Zwju?3t^hl9s745 zbNa@<A;%zCT6kCxQ|z2zTWc3^C~oTYh;fvHnDqqqgAjr;%$K zEZ|QfdXCNii5V`0Yq&++;p8uCZB`KNP8E@vOM9H(F8${z6f4h-BI!LHb%A39#9ehC z!`P|*T>iEz5k*jbra3?p8`({w_2pb){};Kc9HWvs48LQ-a7JL+$G>&6^W2O?!0=yI z-u~QBI5P*N^fWm8tj2UQ6Z{KI*0$=OzyNt_zvar@B#$`FB3DYicO8UF(Y;RuR@9wN zF2Aq?_CMuLx5k(>l_J>rFl58g1jF-Jk;R552;Z*9dlD6>c*t1IX=6x2p}V~KfdOea z#eGo~3Ee+LUfBEMRQoE}+UzVs)1N43j3Oqf{HNf8zF7>*#2NBm7BA5)m1 z*^?G0H$`he^O?85_j0zgc`(6 zNrCeA@rc}(2-go2ERqUw&H;Haga`DM*vObwyiBb#vB5t?v~4i{B z7kAzquM7Ul+c%v;-;D|^z+@~KR!1|It2jgS`T|QdVv0BRbot^tyvTniSH9gVM9<6S z5}?Ga#%9lneAr2?dWv5uD6&Bu#Zh{nx*dN1>@E!Ft^=ldBE*Q+;W*hlcjJXwv*|a2uqv7mSu&?h5+P*wvVj1t}7)I~$@kmI(3j zXUzF!=Z0}0F@S&a#rcl<)FT*plqy)p5o;xpMy^UL{3@CrXeQEUDnE{ zE~4)V5hXd6JD3bIXVMk#FyI579B*q!JBXn$-nXsb{QfcIkOHcTOg^sdF)X23r!Q(7 zD~$3T!Ea%eo{kGAVo6>eTYWSe-00eu!UrZ7)_>s zg+^k!13}GlHZj-;r1DTRvEFgNn2uk9pf=JF%}U6oO}rWU!B!?N8{d9RsQ1a;!s@!x z3Cb@gYsNaN`{j?BxcqGStG5%MVl%w;Oz?!RF7BMH60^`^Q9L{GxKD#?FREL>EGIkrDb?YaTRQJB%8VVa0W=tus|JCE!45yvzBj`*y zZy>0L7jW?$-Lqr@pc+o@WL4g^ZjHF6S&5-@1+b*5>a>zHYeZJimf3Tovq)?dvRVg} z@0Z%O=sy7nWbhA4U!EmMybSBMG`d}@P2jut*CUCqVR7}Kn*PA(GW-}Tbzq{G6V-1T z*QZ+HyM&`up#9Oty9?gy)dK}nZOPp`Icu*$e7X0bUrEI!r*0{4aSfegGM*gkB;RyL zHKL(G&~VN1z{n1m{`tEyTf9Mp|MkGZPONljqgug5A$UvC4Z?7xzH}gv{0xpOc_S33 z&4C5SDz_1GTnSW|XZM#gS`yQmd+E3VxZqAO8YiMW$1qL!NGQ73f>;f&?4ylbQx$63 zb#(fO&yW7ZTi0O&al3fL4>h#Q+t)C_1D4u8=lbg^bJG&@0^VnM;*2j6@GG$NO>z=K z?m;8jDh>uvskb!{40?f+R|*sK-~Nub`8Tatsz zq5(Z}Jr%E!WcLofTpSpY-e~Sg)QU9D^9$;e;(E} zN;u%l2YvL!TpQV%B`?p6r?N4%5N@53-+68krrZO=dE-aS*tT@*LyfdENc+0?%Vktu z8LtF2<HLmjYJM(>`6RU?A&%G0nRhPY{cMrJF((B3laH;o50c3xG7COvs4z>4J(sjF>?d7AP>X)w2PjyQYpB39l}5ucH2>)3*&t{H?gLjqnZUY4D>BCq#RR zYBh_kEi**rU5BC}FTy9Bt3nDI77(mz7mp<>)sZA9(8c9@DYy?BC673rSXE2HO1zs! zS#~@-6&vSbpp|C9GU|cTQDp5ac!n+PHRrI&IMZmo8{Ac!xscV_Ex%Xz=EpkIpcV*O z(aHo%e_#19$bs71VM;{cjwS&mAwwB;{Cw;R?i2a9DGLzXFyw%Yk_l3JdW*!N+HOuA ztFl{78?^zK)87dDAb{S_?C2|sH`Ve@vDPc z)%{Z))L~V;Rtx%1qW@Oiap{vs2?i9vdY(-p30-DP1vVed>f0XP<|_IOxl&XzZxxXi zp4NZSAK5_s-l}iDO*=P!ZHl+WFpLw3{4Q|<@t0nFev$MQJdumhcvv^O2$SCw$CRL&F*^yaCF5a9 zamQbM>&)D;G~(luG!mFvi_;;pH5lSK$YP`A0Qnk7v=o&T9r|5^T3=|lGZ){`>&FdO z)fZzDen2Q6P(x|!7H2{^vjrh~fkBwwd;dcn=zY=pWA`Gd)W~xJf@2DajCsrzA>t?; zu}G}`jVfe>q`t+^A}g9HE0RwOfY7X@(m$&-xsBj#L;e)5udolc2O#`l%3;l#{!jn& zWsD@;AYksBIoelfT!D8gb*{ZQXaM4_dEj~Rd;}|HXg^yg? zGtC8NGd)LYn1H==c6<~^_=Dl#$0O10s|?i^>w3VuJ#$X#3`*iMk{QYLqMLEk+8;6t z)kYgP^gPB+u0)doXCfdVyMm4t`QAljZT?!lIKBwO700d6cE1XXya%UXw7`)kwdTWV zF+U<$$AjHM<{?W^isYPJg6m?dDt5;v^5TSs3|xVN01;fV*YIJltk$GjClb%v!>I(^ zO{ImYx#h6t*R0Sa=d`kGtJ-%?@p6dL4A*3_20!{`N1N<8BWU=}uyy#Ev&zKOW47Xg zqoDkhBxRHnX41+aksVG^L&WO6KKCz@G(B)tDWlCGeSLWHJc)TmyptJhrv#sOKY&B< z9{B$)q{aUK8`5H6WdFrK{5N;$H>AbP#`K@+e`d588GcC+|C7j+tpv@k?13exfoe$< zoM42Z5W^8hpR^G#57YKkOm8|piETO^oz4Jb;S?2*Id2kX9HrWi(v&+PNg+(xtU}SY z_`a^)PD8TCC8YLo^}@aLl6Ar{!*TQT;}%{ z8*T{`91b8y+ArAZPZl=x9(g*QPv9KPS#8V;TWJ?DGNC-mI9bE%1S8-MkvZBZS`{KL z+zVnH=$sADl7iT^h z=o>!7=cJ3s8TM|Wy(Y*@q5Ixs0)Qx9${_kHXF9l;zkGfEch?#hzf*iQPb-s3N;dv)AEX?Bgtv7`<$#T-o+EEo1M%W0pvv&VT#h2MZc89b@)9YyeUS{z0CK^R1tl)tp@^;UZGPjMeNn(AWWfSBIEKBc5;6c7YLc@X9XO|mGtut~mP?B26kwWBgbZd}*g5;mlT z`@7s&eb@ne6ls;toxc1UIT)T?^#|4SI&P$&4;ko3m#V05D+9IdYnr9=Yzz&*D(O#_ zsyzPjde@rU32a3RPr9naluExi?3i<`_SEY6`Hv|83qgouv}$Z$%px9Vn7kpfa&s%} zPEAYkD(L0d>a{pGb6K(zRQQA6u^~&X;8l{B=UwA6+N-8!D)NGRyJNekUG<~T^2$Y}pywu77a%2s>S=;II zr}~qn#g^+TaXgCBWw)x`1ibZb>!EO>c3{N~Md;1yZCB?;SDx;2o5;wENVBU=LyZ#a z#0=LFTD1*TQ)kydKpmekbqUj7G zPYt6NOQ5Fyq%_OEv5%%bYo5S^{Smj?nmd?B*lbC`A_&>tsWXAOS}=Ye9C$o2~}Rjra_ zkGoT>R`ZdPdc#q*i{{1Rv?-l$9?5+b=8LzKH=Ia@p20un+PxFUQXn0JiY;~)U)Q+Y zeOj>TJch=$S>+_~X!2^ANI;F%*Ur8nD<3N;gim+%R6;i3>1@}SZbFX#4`b&LBnl87 z;k9ksw!Qn-w(YmJZQHhO+qP}n$V;Uvl~m=BdyO?bRsr8Lp$kuqkNX^@_9W@!K%Lo ziK}Cean!_1ROg{%V^yKNrPNX>@tb#84HQT*vvdEA%FeFxTMui^{KF=j!TTB2S;JtC zm9tftF7%FBMroA20bd8*qG6S&=Mgr_$jXW*2676&C)dO5D`lk#sP+ZjD-(%FhvgM8kP|Fk<&CGujpB-?OHQNLbD zUAJ6I@Up1OV;v$qoP-Yn#ISF4=NCuMn_>CZd}|fgG=^Poxo4Mu_oXy%j?iE)2MX); zk{ras(@o)9&CnYJn?_B?Zt3bw?c`>7+s=L(VGTWiVu`ub%ke7ZbnxMVcPC(fDA(^A^Nf6J7t>d!kv5i*MJ6P_Db&5Y}G><3)dwrtDO9Yqw% zbiL3~^Q1`gk>IzO3>5N9MXAz#9~Rh@tbE^>O3rr9!Z=vV>UAmq+N68?bb(WtH?~Ze z%kr$ATkOB)MFUAX3@^*Q@JvhbEOlr+9EEU*&u7~%kMVugmYbz6fkmRXvM60ZuOM@~ z3^ioAV2xk?Iyv7pq+E;q$+)%v)kcOyh$(ZS7s5{#jbkn40_XC<*6jLK4IV!Co(wNE zlVS^BkUL8Hl;I54S+m(fe7bB6p;QzzQxWG-oFx*ab6RES^j_S_q+WB=&>Gz91Fc`q zTmVfkkzu9nzqT-BT4EcMMf|d2)W6CAL?Z2vdO;aBHv3(AwO0y$ogt+epH_-Fq+Z0R z#oLdK=f-28y7CWbyNb^XtUQnmRh}j>(7D*QYJa|$>!s$UfA09SozC;B{J&HX=l@#; zvHvFo>%UbHBO@onKl0Uoo&To}Vq|1uU}O1TN~ov}*v@pjg$--dWd{RyvnAWfYV&OE zUp{l8-g=XbyCr+;r^ox1&-L~Yd6F1en8#ono);u`e#ZT%AKJ^J|n0COsp(FfllrcC}Z3AchQV z##dDYChIrPpTC~I$=o+UTK*%Ko02NtF3t7tygUNu~ z6J89O+3~cOxxEeeCmBZn3{ak-wXUVE6+TsV6-qzL-`t1@00A0%nd6b_K z*E$Qpr37G+U!I#CT8r5)tuP!?V0~t3eY$V{NY0!XS&JN*yv7%rxP|~Y4~4y+)t;pe zjG_%gQw!r8Spw9q(T?pG`4(P@>_(LA#TOSvU;i&m9D3q=D?!q)$`rr%`48;f>$yzS zFXA12R$6vpI;7vi*7|x*WX?}5N{Mgv9B2B%dIpEmcV^>5?v$^&v3|+>WW$%W#&3(2 zuhvU1?dWdGBmLyq-L3MshC$K07(T!+$Aw<=7vWnk)jRY+-_X$0Zi>inDH)T3mXQHB zb|!zdV}OA%0EQL@Coqi66pfX^*)LS_fq^ZMJ_tr;+RvfxO5ghY0B+u}z-V6|g#O#* zYb|v)e-y^%x~AtBy7o-p2#7!}RbdrL)yMU&$~%2jTwFGHUt~(We;}m5M#csp$FD+LedITPr0aKR6=0I@WCqmTZT!(y)-MaH zz&pQu*$aNc@9V(VMKU zFV8PK)))QuH+Z4(Z==EQFYCeYW&e+D$h$A&z;9*$w=WEJcUolg^RDo{uejVXKP#1~ zzLCN8JwHfg=GU_}fIQUty2kfuPEq4OHaUp(bk#2_2b!!Q+6~zV6xxiauQ+p^3)A~( z$Az9u{ZFD`|IGM-EtCZ(=Z}%p@YcZC#LCb(WZy%qIhn8e&udgci>S+p@3Z3iyBstO zPV{f{-D*pGWZ_}%>GUr0=jdzhYYCd~1v8^PJ7a)`P6P7+TtgM~gEQD#6yGQKrh)4USz^0-LuCy)fR0W;wkpjjVg2P0Cd!9 z1$<(YNQV=JA(`-%Um6SypR?yZm9V6r)~8MpUS*oopV7}&{M72_ph_2bd8~R+JHYc; zM61W!zYC*A>A}zMPs#T~QJ+FtWYZ9wc_7UjV8E<{RneAVXZm=Bf&MXIPFO8j(vJjt z#E+--JQFhJ(^}75<9Ii^+>Ov6rV`oavyQVUC!K_y!&*~Z@e6EOGHM>o?k?M(t{0vB zRn(+rdsK0&4&9-UCuf;5Rm4fY6<l0!YoOIX^Uvem`dw;x!jbVO6PHgR!-x)boSXL)#-hn z1q{;Q5YhzyIL^82ut?q9H%CYigGY>d8<(ht3SYvw@yXrrD)J2A-LOD zgqsH-NB!w>i_`jgiT)ATOo2EG(6lLX5YsK5NaG&xTo972g#HicpO{RYXH9$+F|SmZ zO}CQb#YNAD!7#nEqfsJ zwDGf_%}*jPvL%Bc$lDh45y$%2(~SsG%LuGGAXu;wK^~fIKmQnNz z?cz2k;QQ>NtbN_gm{FM|6b)c7iMiBgMrC31Ho9rI*xwkF+kP#Hk|%FcH8`K~`o{}2 zAMWN}ar!RFLG6GEQ0|{aird_c9F6bqrZXIW?&~`y<0mYU^cF+vUi!MjETk?-?~o@^ zT2vw*)uvHX(VY=BZcAXafKvk~YG;);w{ri~huq_7<%V7P%Vx&Z84R*rY*4BZE9d>O zs}s>uu#-!Dq=o6%=dW`$4J^+@A28h)wcLUId!*8Ct!Gq0u%Lk2w2yL7t-;>1+K6Js zLI}44d!rPy=MVxb_Z;01btG`l(du(dkUg3N>O?`XgP!O##;iL-CkO_7z^{sf)88=kMsM5Z<$=e9uC)*n={|7L z3SMTbps5&P-;b&6$*bK4iGW{HMVSB=JTn@5RqJs|JmghZh@vS(yyCTKV(YC;2G^tF-< zHt&x9{g}4N%piTy(C6K@nBQ_V_Vf?S7gQ50746glC20|O=XncrO*OjOk7Dg z8^U6hAdC*)de53|x$Y;1kL|Zp>F(}$ym#_b5~&jo2lFVOAS#T?9`Ex>+LC&^k=;x5 zraCfO+eVkyuQdE)b2!qGze_36I)cI+J1UP+b%Xm>XgJI|du1>ax*Iga#IUmFZ535r z&;Ycqru>*h40hpy%?Yj{z95hpP$UhX*ZeK@&dVZXniU5!YwuV6G#vs+Y4OjL*orU> z7*BI(sJc$!z-7}7SjAviJyh{&fo5ac$|g$~?=lC6Su?VP4W zh4|Ln0iale2f?Zc313!vZ64UYu}`*yV>Zo-QXCmTNs#kVkZ1>!KLaW!obs zw1eQM6sV-<5EXI`$7p3m^J{30ew&|Bo6nn?_sqvDntmUSrJhr%=1&BT{aUr=+V5)ycGI} z_~E`J>Xf6Y8-a0Hwy*M+LPcf3_0hF$5vE&c)n$Cl9u2Q(LJ_T=0a|+JF>=oQ4)5CnlNANCG?NT+rj6ae*yE<%^oIIFVr)f4Zk3d0tCjhrJa}H z?w17u9{ac{Olwqt-l7;Sm#-`fAE+8viF*HKA@SbTe%LSF?o-RL$}3L?Nay z43{sQYc4t;sjCE)90BLrA-A`enU|Cuq8kQfCnOgMhtx z7{r~_bm!G*NsXpWn3LopO48`lIe^`Yt3RcE9r4(A4e(m?i7w2ah1J*6!+I)4`iCvgf; z4prrZ#`}?|Cs_c!$%kI+U?IDOjgP7n#PNz+M2iXX)(QYjGmta?T!}7ra174T>GMr< zY`gyOcEy!G_d}V}74{q#$u;sEJCQh%r|RaA)NK&|3fbbpKfa9+d(|4}_B3b%p6MeV zL`jAOdHY#4(yLwAq?m-Z9F%WygO`ppJcI+<>39s7dHEbMI~0h}{z3fx8R^kz1?6Q>(a85tJT~~`)F5^$9UqxI$Zxv_W0CDaA z!!U532IM+oTZt1qiQ+0Z0FX?(Ge0S7HT+%14R;AaqdJ4=do(#s^5jyD>a8savFWUG z>HnmuTK=b_^X2^yo55C1LbE2iAIRt*A^$OPAFsF z7h7?9CkN&3an9Z+3&Q<(4(CQJ9^!PG7qNILq zHi|_G>SCKY$^F^Q4_19OtEml9`6!PmTx<682M}!Fp4G}qsaJh;R|NMw74h21zEwl0 zXFRu@%vt`@w5O+bQ~q)WCROBrg4BREeMA*=co|TC}QhmnNcN{&yNk|;&2<;x9yqq`(9tdBiE(kY9ci1 ztlnuazt5OOj`!%T-B+`~WeRhE>GLh5?g_He$YQb-XhXtAul8Wp;Zg0ohxZ7q-LFv& zf>plp<;{uaZG$lei%_fxJKv51Ey6IW-o^>Pf%&dviYj4(y--|J^B)p`#nIvHpEMrZ z?k+UlVH%C1jqUCWtG3_eT~ca#VKiE;Zt!H{s19H*8%p8hyC&4wRIRS4&Luu}k0nX| zuDQ8jBSxH8$n0Pt1iM>rkAB_3Y$mZ-duR()KXzfhJ@GmOjPTO<}L_ZAFMsc(6BF(hVqW=51Ho|p>#4Ralrw?Jy^qTMBuZZ z72p`R_v9bP%+d;j*z&6)0Z6;O6Jc<(ZxuYf9%T#{ERWkJOynA~1WUeP13<)rvhtq)C@JZc6Kn zW2TswDwQC~;*H?Ge!-Vi__ZMo2*r5flf!XsPl%kr>O&+5H9Lek0Dis5Ht?4J8>;Gd z)IUDV1YqLVF(N8s7;W$1|AQ&Ay}};vu?PMnJ1&wWuKHp@LZc8H8CYQu7qFkKc680< zvoNgTRT$M>sOhwWI1vqV*lJ%;?UP}WXf`9$2#KH8J4C*z(0w0tDIAu9Szu%8$iZ1&fFEAV6J=d%3D z-dlO{&7luAiEowt2)ffT+PHYXvjd|U^-^hb8l0o@UX#n!MAbRAIn%96!6Dc`x#toz zoN;bFC@#QPw;a>s6CB-NM~$0AXgMTW%Q;F(iG$ge)Y@-`JD1>+ot9Zpsos0xn(3E5 z9PBDvCqw~C6$$DNJLNC{TW+{vTPm-SdDP!EcuYhjla=5}>Q9_CK-X#k_Yet+16OG# zBX=K8(326C$uzc0B`gky0dG^|x_XPRi`L2V;w-dtyIm|;7ILlWqNDLb{g6Ik@&}J{ z#ZOM5aY}g(!SVcsGdM%?kvm|)_!EFy_oE~cS<#Kyn|K|1-0luB(Xw%@k0v8 z=Hu~GVtU%&YF%jzHFkjtu@0ip5e8d5B#Z7jmh#1>owOT-$O<9*?#y&wOUiW=)Af(0 zyRk&Kh3^``0Qt3IC~-+x98y#j=Mz^c$2}EL-oF4m;emZ5o-LD^+n5T@PoKeCE+jHb zQQbetVXpO``9jCj>+No%EPvQtzjvGf_<3q*DlL&(9vG9w4&Y8_EcBr*--lmJ2`#G5 z8Y7y4#lX1f{q_;_S;eodf+TrGQGJp_qDKge8|)g}*Y4LqCH)|49Oz^gyDTI@L!p&K z4MX-G$jAwnQ#gsMHQ+VHHDY5o35d%V$}0L^mOup!Pe85s-Y_acBB-VQ72G!a1MT#_ zbQeh-_c=58plzD|&CxLb>}&~OX;mYxVce8JyuGrUh`z5ij9Yku1XEL>;QdO-luF5j z52^n$$pGAPy+V!n(W}MOYus^E0p@65oBW6SPFc%;GfgO8W8r^i%?qtH4toaq&he0% z@I@~%73|W;VJ2|-k#t2&%N%>yk6IS<#2nynX9<>yF>V(c z<<7R*S@9)&r~a`iT4uaVEa1$?B}Fa*(fBpRUHs$m=3x)}>41+PC*}@bEnCgSSl2P~ zFGuF9B)xng@)E^5x1zA1<`EP|h0weH*Fxb0X@6>tlF?#2ZpTC6-j7?fa_Q2j<1Hv=LkQ%{VXB8VctH*l2D>qb(E z-z+He1zY596uRWwr#|1c#l7NgrF}?Soyl+<-1jGQERrunH+^#c-<_~8u?r0S>`EC! zW-1%Xs$p9WqRAaOy|u*w5=#H%`&dQ-F_gAJNeOiHZ;C1ugctYQsVouGfkMxmEdqCy z8Y=+LMc}B+Mv0F(2h?QpulN1ktT5#gDT`WJL_N3aXo+gy4|zp(d!i^VE}Mj&9D!YL z9SM~;a%YZB2bemm{|jd-xB^#10&o{xO=27gtwT~Cs#`_JwJToAz~iKlc7l;PKd~S1 zZ2L-{j|+`|S>Wa7jDbWY)EUH~CPW}(sq1Bc%w88E@Vv0K^bg7XmrtCKoor+cEcM*u zt2X$fn&zLaAbD|!lci=8kK=e&#wiRjGP$nzvY|7fiVTwT>4af4$_ZgpMzYBFR*FFL zPd>BkI>rp-vdC+C4gP@j2ianz$=oEw^V>iKQIZx_2R-3Z*1dn)fKFT0{qjlXxTp05 zkkCJeABf;N?G)LCK1HfB=tY);Hlf;TOnD$8n&A@75yC z#$fz-=kkw*NpGYg;E|qStS{E5CJh5!0vnDg{0^V4RZ=B(3GARKNA#q%bINbvO4ZpNzj$H zw3CiocV9v6Ev95y};8b9>FPKgiwF5Yf^iM=W*Wz!!cZB)CVW8ZuLoh2)FNOS$7WYQWIu=@JUwa7d1W;sNWq)29ORDN z1U;7C6wUQW8SvscMgx`USfradlVIm`61$p!Ypy6GpQi89AT;tQm`W_2+kV)AmWAN= zqpMY-qNk@|#LNQ?`bBD8bv=B~@oSIF0F4WQIOvC9hH1|qfNllh7 zb4M|YN6rTcaam~n!$f>YSP~!1T1b9$HX<;*smr{eb}u`LCT!3+b-+rLe5*EC|F||C zUoxAIFvy7_ajN%MuJ?`#Kh4_+d9!^mDXM+*Ko&G5>*CsUqD(DxJWbUSHDo1fqZ*&S zpXsBU4(t0&37$|Y?k(MQ*h?{3BS}okx!8H?>AM1e$3XgnA|`mFp>pg?S1cOCe6mw$z<>flHHd zsPDYA12XFa^3O-X`+LWy&WV}|c{J>wg(@+4oUOk!imXZN=E15+vFW8U5rzouVA5h- zJJ>WPvXp7q`FKkEAcRO47)@^g;i+2kYXjK3?QDFG4z_;1Mu1P@9Bf?b`UjXFI(Tt2 zGzyhd8oH1xPJWqdi>zmNidF?{$waeWP9T)*lGvC#aD$O^i0ib({En8wK;RB+p&PFk zK&BbwTRIPaseb0>cV`GDlE~^d&_xJFM9!&lk*gf1{amnQx=_@JF|;+_@Zng>;e^}9 z5OC63vnJuCmK%V1b}vo{gz*Yp@c1fMpvE#B7X}-f*Q|L8G_O`7iW+Fy!NqrIZDj1Y zaPOf=Ob@rGm07+V+@qG9 zNE##%8MF+gb;f;8L@JbC1cU|6p>R)qRRCJlPfq?kq)fUiN*BY%5KyTVi%dX zd!1xU7O#N&4p_p`hGitenID%?p|UyZk_SQ#fZiQxv53bjWIw|L(XIP&v8Z(4KZF^? zxTm_3`-&J!mTBacU}lkh;*M=^*on#^;~SEvb&=b=8=a8dmWghoY60!M@K3NR^)h+{ z3S2D+Qx5T%5xYXt0|tTHunv%u1-@YVmXO*GrjFEDv1B{2?T)GB1D`(f(wd|dlBXB9 zl!pVyiw=%+H?xFFm!|_$`;2BI_?~X;x!oE6&TEiu+jLWAvd+dC{43veo6Bm4&SyxZ zp1Z>-eb)JxjXwbAW^W(n5laS7^MqLAHNql`tA!XW7UGcc1vImKxkSLPt9d|)- zseLH_OCMk-%q2&5b0rueYI_UtD0;rdQ#Wh$8(Dnw)*1P^h!g0nlO4Xozcqj4;vPb>weB z6BtgYXze-6(bWw;4giG8Y3nwS#o$UZOm^#6Wb%wS9HYjG@ z;ok0GAIQgygJ$YUe@GPYFrO;baP|Edp!IGls6njVqL-F+Mp*0Gscq)zt%4KNoQ@{R zsHGhj#_Bw$?V=jaCc5=TGA_U}Ts2#}Ys%0SwbE^*#|sndHM(AskH>$@)qWOW*J-_t z57F(|CcVw^(6 z*4C|@gR<3DUe%*JB)R^Gd7=$xb5S}jpsSI`FHzTBQb$&>ex5(g} ze&V8hT1pz+HRIgDSpJIU;kfE~lyWPBf2Zfyq&|jwN;65c0?$hshkllBv%-}j`DIL zz)-e>>?=b2*NfB5*c~pQS<7Cpjv6SOZ5&G~VjH{>c@a`zB9?;?CfClZpyw1Ht+m*) zFCDQg-0D_7;JsOUJp|S7&H3;i?BXImyU6cPQkN5yt4?6w!j^QbA~FKg>65PJtaHrC zuy4rUMwqn?_pDbpNdAcuHV88l)2^SlfPj2e?gcSYc{_>UYq%Vp>4aSu(Q`}K%TrX( zQc0;g+$xLb&f|LxD(9tsc>^0+KU)}>e`q4Dx0;pcJ=9wQ^nB#MwCWc8UHB|T>TRe8 z@$s<^Ve}yybw)}(PUC%($0g^Qj5lFgHPW~8fopwe2^3+&Ya5HG?li$Io=Xc8o$ebk zqRfo!5Z6rci{F8W|AMwpPE`j`^5y9Ld(_O_=EHtiPk^pymaQ`>GqDTEuTA(WPS7V{ zvG$Glr0MM6n&ji{3=z7Ka?Yx*;cf23EIPC(uA9$SrOI-YTbi?qsiIS@1w|lCRp!{? zW5Ts;5}mswNsC@50^Al+6eOrUV!2c^&hu>pq)g{10C0S`e&8rU+oW@&&vlLRsu0KR zthqzY#b$M%$y)WviQI;Z^fCF^Kl_QLwHS-Pnq_~`##_qjj|I6R-S-8xFwPMoD{aL= zk>+tcWhZTZ;mV^7)F@KdTW_7(e8j#pR8|Y`!K8lx_~Y;-oe~u~uOE=SLNsqFI@A7+ zHk`S$ALlO>DF$`Pdu>d_hK|e{X6~Ko@9~-{|5Z=~shMi|OAdJiHmQMb+A~cX2k+Is z5PcGGm-D?TROprBg)?aU`!~A{UPDAR&k6+!k|2v(qk8w3L;apr0md4;x!OJQ_G>2pFsrUpbw<_SB8X1&2xwW;-vBb}XsCwf`8x^!TLkQ& zg-{PTRmL>!wN{0c5@k#jjL7BU0i^mKNgukyP-YV1x;JE6KrN9uC<^7J0R+RxZekxf z15f;3MH`abX5ZHg*{&f$tl}`%I+JY9llz?#LR*2DTGaBTOf`tp2OoT29!N&W2+r2a zvR`yzyvgL^BmUV6HqaW&&`Pmt5yjvgingAPblC5;(?KOKJmYf4ucsouh#-iuGT*>V zz{EHL3pZf2Xnt&>hb1qdOH5TFM(b>i$>c3H-|To@YA7z$W4H)ReNI;$1W!1 ze&QHb*bnc}1?lzjGH?3rXB#Jj6SkFZq;Ts9e$C z(M93;8H*SLx)oG96;tfZ!sVl;rq(){tgYbWDpn0gu=xyfGMoFr5Trbm$hppzZk?|A z0`0REa~s~9B&`G&W*2~Pf(A&{MFwjNDycLy`n7<>93!}oF>ZLEr3c1tSv+a z+uduF|E)e>4j9S@_kFz(U#6vg;>B#G$)!z#c}tkl@)Nf0jlnYQdOIK9SB}h^iMe{8 zVuFYfs3rh!RuURrIKJJxEH|mt&S@^~TpLqW`$%lzb0w9P_sd!5$yW?c?58I6N@PJ= zrqcv6934Hl+2S}{XE)Z8^toV`z`(=zhkZZRu);m}Mc;8?#233MtQ0idn`dggiNr*} z9wij0;IzyE9-Us3zEKPd}6I|V1F4w9TYX`Gb~j{Ou~S0(2b)3%sux;Al(-oA7=Y=$jh9fe6NgbVwl>J=lnu!sfy1Ll8LL8M;O* zTW5WGpf=(%xadh_G@^FYzcBdDJe(R?ot2w#yW&nib=`-gZyYYo(zvoQQ)-SQG!bSh zA^Z98YD%W8Uc|H^u$EHN*V-VSe9Y)0=yz5vyM-!6Fh@2!t0%6;Hnnh6qb%n4(vuMa zy;9yCm#M8X1Sj7;RMLpL(ra-lOjsy83rZ2umJN2)ZMkf0;SsnU`bXMLD~aOPsw9Mt zryb z#F!1UwZ=>T88})6Cj@ir!uE=2WkD+Lin;N1&hWX0U9KZREcny0@I&0Fru>H@tU)ZR za+fPRX0!+}X4zZ^@{0I{#rX(Y8Y{2gdb}LWYWG$gNSio`BuiI5x&C{E?I0{^(ClUZ zMF|l()~oN#Xz`#+Fs)DYhbrPT(g>e@z1kxwv4Q}qkk*O$;WWf9P{{3Q0iY%$JHo4f zO5?;B5B>eoqWrM#eqln&WTF>>`5O$o!tw}oZNqu~Ut3zi-!4oPDaxn$E&(tPqZI23 z*c-H07f2V`>AH*5__vQ?7w{Num2N7%zS}Sc#P`OSs=k;j!IGWPKBA6nFkW|RN28gc z9KkM(w_*)l?9f!;pha6AQztYd5ZEe7K2n}H0iXUQI7PN^5Zj3K!7ic|h2pm;Fhn~$ zYCRyy)r<1{RdyF5H-L4NdU91MVF72Y(Md8?oTiAtlgUt;IkhStqAA2AMV!uOdB~em z+pWemr%Z-p=Z8#MJ*ae(VI3OWIz}Gs6O{$gBHdz)%NbSJ4CSn~>10uRivDE*g96iq zj)5}o`5+O|AEYd$e@k_)&gxK}n!ie4^_iFmK$c`Rw08OT&0G8qo=7s>FiXsq46ua7 z&R#JcPbpm^7LX$A3#_^WHRY%f4ycFLwB4hh3PJnhY-ch}So??`8Aw#gL#&MBrSpu^ zN;<5bX!>ALmR5S1u{{$2qvx}?c!A>}On993WnVyXq!RJ9v)^CqQuZ@knrAs*FSrbi z*xog0cNAcauD)Mn2W6TcN(=FK&pIM{GVW@=8{C!M^2(tQPyu1btFTQqFa|zK5|lR3}yV2vs% zoB$ciGu;67Gvvexh9iD5uwNX3=m zDeBAIAeb4{ngs(>-h(y9Kqo3yP>jaILho#)M3=GC8;RTOpEz`QP?D5^?Tv1LYspBc zEF2y$MoTQT^r&{)x~+8L$tmE^--swmifgrpwq$OYe8$qJcQitIZ=-b~8IDJ&sySkz zOuvE0^ET}{zKry})f%wkFT7y*2Zy)0K@4Qs~ zT|mp^TDbl-YV1EK;0jQ9Tt1JlfvgzrD)s4=@N4YTU^MW^09Gi3DJreR&u(#>`R~96 z><>Nmtk?GX>aR84f3uVZjH~|{?h^%L&v$ZT#6*q`k*cz5R|+n*jhyKspRX*_3>kF+ z*PJaws+O9b098sMs3@E#gYjn`gbX?Jn7|`ZQA#dL65ypDfz+@&%Sz0iFe!Vhs)oNSOzawCcU}AB(4ZUNo*9?* zczJwf+Poxvh&qC6l6GM8y!jM%0_jD7mstI}1aWnvmYpb`@S8 zfW1ZCicdtutCOOhW7uI&WU_y^0H_E~gC56b;CZecB8=v?BR$hhr~ z%tXN1YtGkIUs45a0cOnHGe}sq87NM}Up|o0j+@NBm_QcVmdO?BAk-`FHya@6m}-ns zG}QYnBFAzGj&`5^ZmLUs1+`L2EDpxkAF3JVh73sLx|N(6zI|7J_M5yahBD zPk|hF6#b#{;!PWcVdmwg<|76J0X2k4BwP5v6}|@3D`!hVXfhJE*IL258mWfrhk-yS zG)E-sxQ>s|3Zs?+2HqU5WDB-e%IMfZ)|#5|BgA!01aXWD+caB#!jhb6z4m}e35IGR z%VXkD`lOkkf`4)`{s%swlmLmXQsrh0)KhH*W+L=dd%dfb39u7e7xFM}*fxxT1t_X@MVYw4EjX#3we8u;_Fa#y0}U3 zX&bCzcOA3cgH@3@iI%L_Z)(OXDxPqDcOFf$QFt>#bq>&+?K|{wZUYmV7jzn zW$&O=qGyTpn%bV0>wO3E-0dV=UWcAhT{7{!WCi1{*(C3_I5DuM#AZ$X!q+ZGH*#pz z=~${5*3+igk-yKym+k!0@#z|+X=csP?$OSFc8;VCD>!XP;tj}oh!)bI&gvREG0G(H zxdu`({TKGF0U+%Xs%VErluin50eT-b3w-vAC96FwYv17C`6daD!65IM8VvZ&Ni$Q* zemguxFW^nu<5;PCPOujS;=?YZ+Nucg{TmunUgF}Ocw8e9bkpYf>muvacM(1a#^^OO zYmz3X{24uKNC>7~Rfb~w zcl%cUPu?Jpc{-WJ3(;ZOc)R7G5^|1Hq@w^qcC?PtJ}!CEV^zFG(~Y{66VY*kPD|Iz zEUStNra}2jih4*;VD1vx(x4GH>CGYQv0X21dnX`W?Vnh4UUe3s%o!-0SPj>?Tv3lT1gXJfBfy!maB1AIt$;oF4vVf_H7KDowq(3JBau! z_3y=1N8y>kFgB8RuCW?HN|-53)pf*vAPTPpr@uKzD7tYK7kAgsVUWB4Ef|<6~bh}l*@sftB)y>VaczvDLQh7u$QSMZ1dD|Qv12`kgcumBq1elRz zIMclnidBUCtLx6vVJYW0=0nzp4}8F(-bVq+MHAL-sp~Xv*M|)M^h^{^$&$x2NqI-2 zjhMK4i@d?#BFa*p^J4y6Ew^Y=-nWk74@MpN(^%tFa((2nVs5+;!(aI_)||sphy|b} zTtG0(^h0h0a-|M9%y@MA@{DQV<8`Bsz!KUZTJq#7)o6|b^UP-%0ilyUkV5@23=e(- zWH$NYqB8wft57Pw>H_+DxCu$k8=<*@ww39qF|k}_rKpPmV^qhSw4@l0OywWUuMEC$ z91}%qN)Fj@ZBlbi?CZ8XXn6E^l81rJrqpTc8mFb2iE|%&rBs&R%Y$lsS(BlWt)_<% z!8Qg{*NNfc_3~7zK&4mkxy&Br#=Ya=uS!l+|C1tpUKOJA&-+EUYQt_lxB411RPT`z zME|>w;%bKKiOmP;br8h}j3&H*h7u~h*Xf=kzp}e(NNF|fNu7aBYCO(a@qpsZpf;RW zzfmrU7n{>L1C_S^s}>n^p&+^6Kxwm4GT{c5Xuw9HcS`> za#sq~XMUp3h8u|fDKuiq*#Zrm=6J~5jLu9yXpZc}t{(qk&Oj|-BJ^Wj#b?bnoCV9t z1>Q;=qbO_{sw2{^NP(@Y9dz{~Cd!L`S0AH}#e^d*&~z(a2r5l7z~zm`f(@GZOJV3i z3rQNyv0$Nw%;kjhSXQ2G;RV>TU^~~vTp=ONXtiamCTWaU-W24E_TFb~p@&%{u8p=o zq(!BUE|m}Q^kVgvX(TL02(jh{8^lcV6w2*T&0&<5X;JS_*>HCdRC>LQaq0LC+K6%$ z8Od%8N9BI)lIS>%%!cz8aSxlYADkU?=1pHdt;eOvKB;i-vSI~blf5vhYV@eam+jF6 z5|^7avL@FYMZol!iQ`K&@TM<{@;6-0FH= z(dS;e{k2ERiViZAzIJeF1T}D5t#&enYcYpI9+F?(>!b8WQay;h&?D7{5GZg$XY#4EK6gf3?P zUNZ!d%n2Unl}Og7`yPW0{C(D@t1t zcDD+x%8wx5p!z~eJ51a?sg)fBflQ3io3pug-md8%ASR!qZ(!XLoT8Ag^-|z)B)tZW zZjoZ%gK+{|{3B~55W34*eHUg`-AMZ=yju&WNS5Y(h{fvycPls=2adfMBvg9D+ydfL z6A96JktM=9tRZR2l={Hyc{74hnHXSyYceS64Z0pXxH!+_LfJQ6N^lQ1w1oK;>CiNw ze>+qt4>4MVki5$11nY0l`Lnvw&}M&8yUTC#V6(m&JC!a9DKrpT(e$OGi6Y<0Y}=&z zoF9RmK32K#0dl(_F9-c3bsrPV_DJZEKNvEAQ^@?MsLnn4IkkA!PsQzJMr$JKyIu>~ zm@DYcnGC#^v;UERYbG&~{D4~DlCunYW6BP4U`F6^Y;3%@mb<*@sXbD+Y5qvu9MC*>6kkJ(IlhW)bt{9S5dd<%86&;6N&vm29j|C-iBOc#I&v7w4(i0e*+WUOqQmmkT zKL!MAn0fGi6X!L8VdK^%-xZbb521=5y3Sbt?z*)?FD=LjMyjWds@v8z_8Pzls1ZZA zhbUYp-yz>QGrr!rme65UH32lYG?!R|D3k*DSCbEUM+s3QfvlL8mfsddQCS3x%L2v) zZ86TM96by1N=*NvovGUr)wU=+V`sj<`bsAsb<-~yZuu_x^~^>A(x#+g%OA}^n3lkh zqXilt;=)->GR|w5@mxt?HG?)gBAzLfM(a{HF(JxAZ}5n^%*;@zA)&EL_Gk4dLx0nO zTVex!tkvZ<0MZ4QI^u-N3wZ23y?jOHDyb5j!OcRy1gMHwBK}H(!d_m+-k{Xz(u7by z>;>C#t#@MAHWSRdLvNmuec^g+uwn#4L-)FfF!jSi~i%u*&l zV=rR=Q%cX63fSQarAq9*k~L^EcAnUxkg&?HaZJPTU7R%YxXoLvknr)30eC2&LNnL^ zi^sVKj6>4X`ep-VEPi7@`k?TLiUG?AEn8Vn+v{6ckn1B+v_pQ#RZ516Dkkg+*ahp- zVqU~A%opLZ#KDR;5(Vv6Pgg44vHBxjNZzjioLN;e^riMciR4~|pfQLbUVr99R2M=C zJH7v&Da}=VaDSZL?GVgfijasn^O*$Syk2LnprZHC@nj{tr*I&^aGN;RYsQZF!9Xp6_OZTRgH zQ}Rk05JR5|ltq=e!P&t|G zXC??dYeg_a`v^=#oi|Mc(;;8prA%5LSZDp#G6SXW=m)&52zJ28!T749`Y8Dd5*;6+ z+3xO&|Bx?}zzH*V1|(cDfW}(FYHH>>ba5aDuJa*-^3vD@F|+Uo)pCk-bUkjWHVQt8 zLTcUu&pg6vz%{z2lXv?ld?4D6Qvzdx<2@30Z9q5o1)$dsVS?|+u(4Zdr+|yS@b%2T z)%8fJb*UEa;&#yw#rBS8mE{v`WKDHMXxu~4OQ+<@a?-U0;Mcr6W^5L9kE|Val5c1( zj6JKasMENz#_d-cPMQZG4a*Pj$N(4kd{qb+@jOHUHIp4^K~l%GVZ~{8;KCUA50W@Q zA6P`;z+S^1z6jSF-($&Shu~?^|8eY>g0L#cJ}YNv3^n~IOSm1Kr&hVmC0Y^Pdf~kP zN&!sg<=4Neu_taC_vctTPOw&OhYvyJ2LQ%GIt^C^su)23WO?p+T=W?R&PXtj6n6$u zFRLllrtdwLS6?I`Jp(k}kZxR;c8;7We#k@cHf6F7o*~GR-B?%)OltStU)u^HaSlU8 zSGggVc>hkzj|6a~)8=~!Xm2taz6Bvp28?SfeN=4ccD9PqVb@>qC_`y9h@jT5vF1$+ zO(tI<0y!Iyo%iAOphbnu3MFnW2CxM~W0*

c&SjU?;i@r0cSk+;8UM(9Y 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

-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

w>>tel8j9d@6nUZ*D+IdHqcDkO_%63fO^ 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%0Dgd|1Tu2m&tu|q$ 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 bazel.gpg sudo mv bazel.gpg /etc/apt/trusted.gpg.d/ echo "deb [arch=amd64] https://storage.googleapis.com/bazel-apt stable jdk1.8" | sudo tee /etc/apt/sources.list.d/bazel.list curl https://bazel.build/bazel-release.pub.gpg | sudo apt-key add - -sudo apt update && sudo apt install bazel=5.0.0 -y +sudo apt update && sudo apt install bazel=6.3.2 -y sudo apt install clang-format -y rm $PWD/.git/hooks/pre-push ln -s $PWD/hooks/pre-push $PWD/.git/hooks/pre-push @@ -27,7 +27,7 @@ cd bazel_build unzip bazel-6.0.0-dist.zip -export JAVA_HOME='/usr/lib/jvm/java-1.11.0-openjdk-arm64/' +export JAVA_HOME='/usr/lib/jvm/java-1.11.0-openjdk-amd64' env EXTRA_BAZEL_ARGS="--host_javabase=@local_jdk//:jdk" bash ./compile.sh sudo cp output/bazel /usr/local/bin/ cd .. @@ -40,3 +40,14 @@ bazel build @com_github_bazelbuild_buildtools//buildifier:buildifier sudo apt-get install python3.10-dev -y sudo apt-get install python3-dev -y + +# Build sgx code +cd enclave/ +rm ./sgx_cpp_* +cd sgxcode/ +rm -rf build || true +mkdir build && cd build/ +cmake .. && make +cd ../.. +cp sgxcode/build/host/sgx_cpp_* ./ +ldconfig /usr/local/lib64/ diff --git a/NOTICE b/NOTICE new file mode 100644 index 000000000..311b43605 --- /dev/null +++ b/NOTICE @@ -0,0 +1,5 @@ +Apache ResilientDB +Copyright 2023-2024 The Apache Software Foundation + +This product includes software developed at +The Apache Software Foundation (/). diff --git a/README.md b/README.md index 709ecfdbd..9e14458a8 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,13 @@ 4. ResilientDB exposes a wide range of interfaces such as a **Key-Value** store, **Smart Contracts**, **UTXO**, and **Python SDK**. Following are some of the decentralized applications (DApps) built on top of ResilientDB: **[NFT Marketplace](https://nft.resilientdb.com/)** and **[Debitable](https://debitable.resilientdb.com/)**. 5. To persist blockchain, chain state, and metadata, ResilientDB provides durability through **LevelDB** and **RocksDB**. 6. ResilientDB provides access to a seamless **GUI display** for deployment and maintenance, and supports **Grafana** for plotting monitoring data. -7. **[Historial Facts]** The ResilientDB project was founded by **[Mohammad Sadoghi](https://expolab.org/)** along with his students ([Suyash Gupta](https://gupta-suyash.github.io/index.html) as the lead Architect, [Sajjad Rahnama](https://sajjadrahnama.com/), [Jelle Hellings](https://www.jhellings.nl/)) at **[UC Davis](https://www.ucdavis.edu/)** in 2018 and was open-sourced in late 2019. On September 30, 2021, we released ResilientDB v-3.0. In 2022, ResilientDB was completely re-written and re-architected ([Junchao Chen](https://github.com/cjcchen) as the lead Architect along with the entire [NexRes Team](https://resilientdb.com/)), paving the way for a new sustainable foundation, referred to as NexRes (Next Generation ResilientDB). Thus, on September 30, 2022, NexRes-v1.0.0 was born, marking a new beginning for **[ResilientDB](https://resilientdb.com/)**. +7. **[Historial Facts]** The ResilientDB project was founded by **[Mohammad Sadoghi](https://expolab.org/)** along with his students ([Suyash Gupta](https://gupta-suyash.github.io/index.html) as the lead Architect, [Sajjad Rahnama](https://sajjadrahnama.com/) as the lead System Designer, and [Jelle Hellings](https://www.jhellings.nl/)) at **[UC Davis](https://www.ucdavis.edu/)** in 2018 and was open-sourced in late 2019. On September 30, 2021, we released ResilientDB v-3.0. In 2022, ResilientDB was completely re-written and re-architected ([Junchao Chen](https://github.com/cjcchen) as the lead Architect, [Dakai Kang](https://github.com/DakaiKang) as the lead Recovery Architect along with the entire [NexRes Team](https://resilientdb.com/)), paving the way for a new sustainable foundation, referred to as NexRes (Next Generation ResilientDB). Thus, on September 30, 2022, NexRes-v1.0.0 was born, marking a new beginning for **[ResilientDB](https://resilientdb.com/)**. On October 21, 2023, **[ResilientDB](https://cwiki.apache.org/confluence/display/INCUBATOR/ResilientDBProposal)** was officially accepted into **[Apache Incubation](https://incubator.apache.org/projects/resilientdb.html)**. + +

+ + + +
--- @@ -44,10 +50,12 @@ The latest ResilientDB documentation, including a programming guide, is availabl - System Parameters & Configuration - Continuous Integration & Testing -![Nexres](./img/nexres.png) +
+ +
## OS Requirements -Ubuntu 20.* +Ubuntu 20+ --- @@ -70,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: - client get value = test_value +>key = key1, top 1
+> value =
+> item {
+> key: "key1"
+>  value_info {
+>    value: "v2"
+>    version: 3
+>  }
+>} -Run tools to get all values that have been set: +#### Get Key Range #### +Obtain the values of the keys in the ranges [`key1`, `key2`]. Do not use this function in your practice code - bazel-bin/service/tools/kv/api_tools/kv_service_tools service/tools/config/interface/service.config getvalues + kv_service_tools --config config_file --cmd get_key_range_with_version --min_key key1 --max_key key2 -You will see the following result if successful: +| 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 | + +Example: + + 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 getvalues value = [test_value] ## Deployment Script @@ -121,28 +319,18 @@ We also provide access to a [deployment script](https://github.com/resilientdb/r - For amd architecture, run: ```shell - docker run -it expolab/resdb:amd64 bash + docker run -d --name myserver expolab/resdb:amd64 ``` - For Apple Silicon (M1/M2) architecture, run: ```shell - docker run -it expolab/resdb:arm64 bash + docker run -d --name myserver expolab/resdb:arm64 ``` -4. **Start the kv_service within the Container** - Once you're inside the container, start the `kv_service` by running the following command: +4. **Test with Set and Get Commands** + Exec into the running server: ```shell - ./service/tools/kv/server_tools/start_kv_service.sh + docker exec -it myserver bash ``` -5. **Test with Set and Get Commands** - 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 - ``` \ No newline at end of file + Verify the functionality of the service by performing set and get operations provided above [functions](README.md#functions). diff --git a/WORKSPACE b/WORKSPACE index 317fefbfa..d97d40690 100644 --- a/WORKSPACE +++ b/WORKSPACE @@ -3,6 +3,36 @@ workspace(name = "com_resdb_nexres") load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive") load("//:repositories.bzl", "nexres_repositories") +new_local_repository( + name = "openenclave_host", + path = "/opt/openenclave_0_17_0", + build_file_content = """ +cc_import( + name = "oehost_lib", + static_library = "lib/openenclave/host/liboehost.a", + visibility = ["//visibility:public"], +) + +exports_files(["lib/openenclave/host/liboehost.a"]) +""" +) + +new_local_repository( + name = "openenclave", + path = "/opt/openenclave_0_17_0/include", + build_file_content = """ +package(default_visibility = ["//visibility:public"]) +filegroup( + name = "headerfile", + srcs = glob(["**/*.h"]), +) +cc_library( + name = "headers", + hdrs = [":headerfile"] +) +""" +) + nexres_repositories() http_archive( @@ -198,8 +228,8 @@ http_archive( http_archive( name = "pybind11_bazel", - strip_prefix = "pybind11_bazel-master", - urls = ["https://github.com/pybind/pybind11_bazel/archive/master.zip"], + strip_prefix = "pybind11_bazel-2.11.1.bzl.1", + urls = ["https://github.com/pybind/pybind11_bazel/archive/refs/tags/v2.11.1.bzl.1.zip"] ) http_archive( diff --git a/benchmark/protocols/fides/BUILD b/benchmark/protocols/fides/BUILD new file mode 100644 index 000000000..575fc031f --- /dev/null +++ b/benchmark/protocols/fides/BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:private"]) + +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +cc_binary( + name = "kv_server_performance", + srcs = ["kv_server_performance.cpp"], + copts = ["-Iexternal/openenclave"], + deps = [ + "//chain/storage:memory_db", + "//executor/kv:kv_executor", + "//platform/config:resdb_config_utils", + "//platform/consensus/ordering/fides/framework:consensus", + "//service/utils:server_factory", + "//enclave:headers", + ], +) + diff --git a/benchmark/protocols/fides/kv_server_performance.cpp b/benchmark/protocols/fides/kv_server_performance.cpp new file mode 100644 index 000000000..32b640dd6 --- /dev/null +++ b/benchmark/protocols/fides/kv_server_performance.cpp @@ -0,0 +1,261 @@ +/* + * 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 "chain/storage/memory_db.h" +#include "enclave/sgx_cpp_u.h" +#include "executor/kv/kv_executor.h" +#include "platform/config/resdb_config_utils.h" +#include "platform/consensus/ordering/fides/framework/consensus.h" +#include "platform/networkstrate/service_network.h" +#include "platform/statistic/stats.h" +#include "proto/kv/kv.pb.h" +#include +#include +#include +#include + +using namespace resdb; +using namespace resdb::fides; +using namespace resdb::storage; + + +unsigned char* key_buf; +size_t key_len = 0; +oe_enclave_t* enclave = NULL; +oe_result_t result; +int ret = 0; +uint32_t flags = OE_ENCLAVE_FLAG_DEBUG; + +BIO *bio = BIO_new_mem_buf(key_buf, -1); +RSA *pub_key = NULL; +size_t encrypted_len; +unsigned char* encrypted_data; + +bool check_simulate_opt(int* argc, char* argv[]) { + for (int i = 0; i < *argc; i++) { + if (strcmp(argv[i], "--simulate") == 0) { + std::cout << "Running in simulation mode" << std::endl; + memmove(&argv[i], &argv[i + 1], (*argc - i) * sizeof(char*)); + (*argc)--; + return true; + } + } + return false; +} + +void ShowUsage() { + printf(" [logging_dir]\n"); +} + +std::string GetRandomKey() { + int num1 = rand() % 10; + int num2 = rand() % 10; + return std::to_string(num1) + std::to_string(num2); +} + +std::string GetEncryptedRandomKey() { + int num1 = rand() % 10; + int num2 = rand() % 10; + std::string key = std::to_string(num1) + std::to_string(num2); + std::cout<<"key: "<(key.c_str()); + + encrypted_data = new unsigned char[encrypted_len]; // Initialize buffer + std::cout<<"Before RSA_public_encrypt." << std::endl; + size_t result_len = RSA_public_encrypt(key.size(), key_data, encrypted_data, + pub_key, RSA_PKCS1_PADDING); + + std::string encrypted_key(encrypted_data, encrypted_data + result_len); + std::cout<<"encrypted_key: "<= 6) { + auto monitor_port = Stats::GetGlobalStats(5); + monitor_port->SetPrometheus(argv[5]); + } + + printf("Run 1\n"); + std::unique_ptr config = + GenerateResDBConfig(config_file, private_key_file, cert_file); + + config->RunningPerformance(true); + ResConfigData config_data = config->GetConfigData(); + + std::cout << "kv_server: reseting prng" << std::endl; + // Predefined key, can replace it by using some DKG protocol + uint32_t rdrandNum = 123456789; + uint32_t totalNum = config->GetReplicaNum(); + result = reset_prng(enclave, &ret, &rdrandNum, &totalNum); + if (result != OE_OK) { + ret = 1; + } + if (ret != 0) { + std::cerr << "kv_server_performance: reset_prng failed with " << ret << std::endl; + } else { + std::cout << "kv_server_performance: reset_prng succeeded." << std::endl; + } + + std::cout << "kv_server_performance: requesting counter" << std::endl; + uint32_t index; + result = request_counter(enclave, &ret, &index); + if (result != OE_OK) { + ret = 1; + } + if (ret != 0) { + std::cerr << "kv_server_performance: request_counter failed with " << ret << std::endl; + } else { + std::cout << "kv_server_performance: request_counter succeeded with index: " << index << std::endl; + } + + + auto performance_consens = std::make_unique( + *config, std::make_unique(std::make_unique()), enclave); + +/* // For transaction decryption + + std::cout << "kv_server_performance: generate key" << std::endl; + size_t key_size = 2048; + result = generate_key(enclave, &ret, &key_size); + std::cout << "After generate_key" << std::endl; + if (result != OE_OK || ret != 0) { + std::cerr << "kv_server_performance: generate_key failed with " << ret << std::endl; + // goto exit; + } + + result = get_pubkey(enclave, &ret, &key_buf, &key_len); + + bio = BIO_new_mem_buf(key_buf, -1); + pub_key = NULL; + PEM_read_bio_RSAPublicKey(bio, &pub_key, NULL, NULL); + + encrypted_len = RSA_size(pub_key); + encrypted_data = new unsigned char[encrypted_len]; // Initialize buffer + + +{ + // KVRequest request; + // request.set_cmd(KVRequest::SET); + // request.set_key(GetEncryptedRandomKey()); + // request.set_value("helloworld"); + + + // int requestSize = request.ByteSizeLong(); + // unsigned char* binary_data = new unsigned char[requestSize]; + + // if (request.SerializeToArray(binary_data, requestSize)) { + // std::cout << "Serialization successful!" << std::endl; + // } else { + // std::cerr << "Serialization failed!" << std::endl; + // } + + // std::cout << "kv_server_performance: encrypt data" << std::endl; + // unsigned char* encrypted_data = nullptr; + // size_t encrypted_len = 0; + // result = encrypt(enclave, &ret, binary_data, + // &encrypted_data, requestSize,&encrypted_len); + + // printf("kv_server_performance: Finish encrypt data\n"); + // printf("encrypted_data: %s\n", encrypted_data); + + // std::string request_data(encrypted_data, encrypted_data + encrypted_len); + + + KVRequest request; + request.set_cmd(KVRequest::SET); + request.set_key(GetRandomKey()); + request.set_value("helloworld"); + std::string request_data; + request.SerializeToString(&request_data); + + const unsigned char* request_data_char = reinterpret_cast(request_data.c_str()); + + encrypted_data = new unsigned char[encrypted_len]; // Initialize buffer + std::cout<<"Before RSA_public_encrypt."; + size_t result_len = RSA_public_encrypt(request_data.size(), request_data_char, encrypted_data, + pub_key, RSA_PKCS1_PADDING); + + std::string encrypted_data_str(encrypted_data, encrypted_data + result_len); + + performance_consens->SetupPerformanceDataFunc([encrypted_data_str]() { + return encrypted_data_str; + }); +} + + printf("Run 4\n"); +*/ + + performance_consens->SetupPerformanceDataFunc([]() { + KVRequest request; + request.set_cmd(KVRequest::SET); + request.set_key(GetRandomKey()); + request.set_value("helloworld"); + std::string request_data; + request.SerializeToString(&request_data); + return request_data; + }); + + auto server = + std::make_unique(*config, + std::move(performance_consens)); + server->Run(); + + BIO_free_all(bio); + // delete[] binary_data; +} diff --git a/benchmark/protocols/fides/kv_service_tools.cpp b/benchmark/protocols/fides/kv_service_tools.cpp new file mode 100644 index 000000000..17858ef21 --- /dev/null +++ b/benchmark/protocols/fides/kv_service_tools.cpp @@ -0,0 +1,57 @@ +/* + * 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 + +#include "common/proto/signature_info.pb.h" +#include "interface/kv/kv_client.h" +#include "platform/config/resdb_config_utils.h" + +using resdb::GenerateReplicaInfo; +using resdb::GenerateResDBConfig; +using resdb::KVClient; +using resdb::ReplicaInfo; +using resdb::ResDBConfig; + +int main(int argc, char** argv) { + if (argc < 2) { + printf("\n"); + return 0; + } + std::string client_config_file = argv[1]; + ResDBConfig config = GenerateResDBConfig(client_config_file); + + config.SetClientTimeoutMs(100000); + + KVClient client(config); + + client.Set("start", "value"); + printf("start benchmark\n"); +} 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..7a81f27c7 100644 --- a/benchmark/protocols/pbft/kv_server_performance.cpp +++ b/benchmark/protocols/pbft/kv_server_performance.cpp @@ -1,31 +1,25 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #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 +28,7 @@ #include "proto/kv/kv.pb.h" using namespace resdb; +using namespace resdb::storage; void ShowUsage() { printf(" [logging_dir]\n"); @@ -69,7 +64,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/benchmark/protocols/pbft/kv_service_tools.cpp b/benchmark/protocols/pbft/kv_service_tools.cpp index 17858ef21..43627b34f 100644 --- a/benchmark/protocols/pbft/kv_service_tools.cpp +++ b/benchmark/protocols/pbft/kv_service_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/benchmark/protocols/poe/BUILD b/benchmark/protocols/poe/BUILD new file mode 100644 index 000000000..e7bbde3b1 --- /dev/null +++ b/benchmark/protocols/poe/BUILD @@ -0,0 +1,16 @@ +package(default_visibility = ["//visibility:private"]) + +load("@bazel_skylib//rules:common_settings.bzl", "bool_flag") + +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/poe/framework:consensus", + "//service/utils:server_factory", + ], +) + diff --git a/benchmark/protocols/poe/kv_server_performance.cpp b/benchmark/protocols/poe/kv_server_performance.cpp new file mode 100644 index 000000000..fa13e9a94 --- /dev/null +++ b/benchmark/protocols/poe/kv_server_performance.cpp @@ -0,0 +1,89 @@ +/* + * 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 "chain/storage/memory_db.h" +#include "executor/kv/kv_executor.h" +#include "platform/config/resdb_config_utils.h" +#include "platform/consensus/ordering/poe/framework/consensus.h" +#include "platform/networkstrate/service_network.h" +#include "platform/statistic/stats.h" +#include "proto/kv/kv.pb.h" + +using namespace resdb; +using namespace resdb::poe; +using namespace resdb::storage; + +void ShowUsage() { + printf(" [logging_dir]\n"); +} + +std::string GetRandomKey() { + int num1 = rand() % 10; + int num2 = rand() % 10; + return std::to_string(num1) + std::to_string(num2); +} + +int main(int argc, char** argv) { + if (argc < 3) { + ShowUsage(); + exit(0); + } + + // google::InitGoogleLogging(argv[0]); + // FLAGS_minloglevel = google::GLOG_WARNING; + + char* config_file = argv[1]; + char* private_key_file = argv[2]; + char* cert_file = argv[3]; + + if (argc >= 5) { + auto monitor_port = Stats::GetGlobalStats(5); + monitor_port->SetPrometheus(argv[4]); + } + + std::unique_ptr config = + GenerateResDBConfig(config_file, private_key_file, cert_file); + + config->RunningPerformance(true); + ResConfigData config_data = config->GetConfigData(); + + auto performance_consens = std::make_unique( + *config, std::make_unique(std::make_unique())); + performance_consens->SetupPerformanceDataFunc([]() { + KVRequest request; + request.set_cmd(KVRequest::SET); + request.set_key(GetRandomKey()); + request.set_value("helloword"); + std::string request_data; + request.SerializeToString(&request_data); + return request_data; + }); + + auto server = + std::make_unique(*config, std::move(performance_consens)); + server->Run(); +} diff --git a/benchmark/protocols/poe/kv_service_tools.cpp b/benchmark/protocols/poe/kv_service_tools.cpp new file mode 100644 index 000000000..17858ef21 --- /dev/null +++ b/benchmark/protocols/poe/kv_service_tools.cpp @@ -0,0 +1,57 @@ +/* + * 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 + +#include "common/proto/signature_info.pb.h" +#include "interface/kv/kv_client.h" +#include "platform/config/resdb_config_utils.h" + +using resdb::GenerateReplicaInfo; +using resdb::GenerateResDBConfig; +using resdb::KVClient; +using resdb::ReplicaInfo; +using resdb::ResDBConfig; + +int main(int argc, char** argv) { + if (argc < 2) { + printf("\n"); + return 0; + } + std::string client_config_file = argv[1]; + ResDBConfig config = GenerateResDBConfig(client_config_file); + + config.SetClientTimeoutMs(100000); + + KVClient client(config); + + client.Set("start", "value"); + printf("start benchmark\n"); +} 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..18eaed71b 100644 --- a/chain/state/chain_state.cpp +++ b/chain/state/chain_state.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "chain/state/chain_state.h" @@ -29,64 +23,22 @@ namespace resdb { -ChainState::ChainState(std::unique_ptr storage) - : storage_(std::move(storage)) {} - -Storage* ChainState::GetStorage() { - return storage_ ? storage_.get() : nullptr; -} +ChainState::ChainState() : max_seq_(0) {} -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..697b1afcb 100644 --- a/chain/state/chain_state.h +++ b/chain/state/chain_state.h @@ -1,50 +1,42 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #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 0d0fda1e4..7fd3fbe19 100644 --- a/chain/state/chain_state_test.cpp +++ b/chain/state/chain_state_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "chain/state/chain_state.h" @@ -28,25 +22,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"); - // GetValues 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..701dcbd98 --- /dev/null +++ b/chain/storage/kv_storage_test.cpp @@ -0,0 +1,232 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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..2cc96fb59 --- /dev/null +++ b/chain/storage/leveldb.cpp @@ -0,0 +1,290 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/leveldb.h b/chain/storage/leveldb.h new file mode 100644 index 000000000..199f1f274 --- /dev/null +++ b/chain/storage/leveldb.h @@ -0,0 +1,81 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include + +#include "chain/storage/proto/leveldb_config.pb.h" +#include "chain/storage/storage.h" +#include "leveldb/db.h" +#include "leveldb/write_batch.h" + +namespace resdb { +namespace storage { + +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(std::optional config_data = std::nullopt); + + virtual ~ResLevelDB(); + int SetValue(const std::string& key, const std::string& value) override; + std::string GetValue(const std::string& key) override; + std::string GetAllValues(void) 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 top_number) override; + + bool Flush() override; + + private: + void CreateDB(const std::string& path); + + private: + std::unique_ptr db_ = nullptr; + ::leveldb::WriteBatch batch_; + unsigned int write_buffer_size_ = 64 << 20; + 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..2d5b87258 --- /dev/null +++ b/chain/storage/memory_db.cpp @@ -0,0 +1,170 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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..372f46474 --- /dev/null +++ b/chain/storage/memory_db.h @@ -0,0 +1,88 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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..963d92bbe 100644 --- a/chain/storage/mock_storage.h +++ b/chain/storage/mock_storage.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -38,6 +32,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..86efd3e3c --- /dev/null +++ b/chain/storage/rocksdb.cpp @@ -0,0 +1,291 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#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/rocksdb.h b/chain/storage/rocksdb.h new file mode 100644 index 000000000..c3e355648 --- /dev/null +++ b/chain/storage/rocksdb.h @@ -0,0 +1,80 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ + +#pragma once + +#include +#include +#include + +#include "chain/storage/proto/rocksdb_config.pb.h" +#include "chain/storage/storage.h" +#include "rocksdb/db.h" +#include "rocksdb/write_batch.h" + +namespace resdb { +namespace storage { + +std::unique_ptr NewResRocksDB( + const std::string& path, std::optional config = std::nullopt); +std::unique_ptr NewResRocksDB( + std::optional config = std::nullopt); + +class ResRocksDB : public Storage { + public: + 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; + std::string GetAllValues(void) 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 top_number) override; + + bool Flush() override; + + private: + 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..76e352de8 100644 --- a/chain/storage/storage.h +++ b/chain/storage/storage.h @@ -1,31 +1,27 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once +#include #include +#include namespace resdb { @@ -34,22 +30,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/common/crypto/signature_utils.cpp b/common/crypto/signature_utils.cpp index e86f99b1c..17dddbba5 100644 --- a/common/crypto/signature_utils.cpp +++ b/common/crypto/signature_utils.cpp @@ -91,11 +91,13 @@ bool ECDSAVerifyString(const std::string& message, new CryptoPP::SignatureVerificationFilter( verifier, new CryptoPP::ArraySink((CryptoPP::byte*)&valid, sizeof(valid)))); - if (!valid) { - LOG(ERROR) << "signature invalid. signature len:" << signature.size() - << " message len:" << message.size(); - } - return valid; +// if (!valid) { +// LOG(ERROR) << "signature invalid. signature len:" << signature.size() +// << " message len:" << message.size(); +// } +// return valid; +// Ignore vertification result for compatibility of public IP + return true; } std::string RsaSignString(const std::string& private_key, diff --git a/documents/doxygen/Doxyfile b/documents/doxygen/Doxyfile index 547a34fcd..13911062c 100644 --- a/documents/doxygen/Doxyfile +++ b/documents/doxygen/Doxyfile @@ -42,7 +42,7 @@ DOXYFILE_ENCODING = UTF-8 # title of most generated pages and in a few other places. # The default value is: My Project. -PROJECT_NAME = ResilientDB +PROJECT_NAME = ResilientDB # The PROJECT_NUMBER tag can be used to enter a project or revision number. This # could be handy for archiving the generated documentation or if some version @@ -54,7 +54,7 @@ PROJECT_NUMBER = v1.3.1 # for a project that appears at the top of each page and should give viewer a # quick idea about the purpose of the project. Keep the description short. -PROJECT_BRIEF = NexRes +PROJECT_BRIEF = ResilientDB # With the PROJECT_LOGO tag one can specify a logo or an icon that is included # in the documentation. The maximum height of the logo should not exceed 55 @@ -1330,7 +1330,7 @@ HTML_FILE_EXTENSION = .html # of the possible markers and block names see the documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_HEADER = +HTML_HEADER = header # The HTML_FOOTER tag can be used to specify a user-defined HTML footer for each # generated HTML page. If the tag is left blank doxygen will generate a standard @@ -1370,7 +1370,7 @@ HTML_STYLESHEET = # documentation. # This tag requires that the tag GENERATE_HTML is set to YES. -HTML_EXTRA_STYLESHEET = +HTML_EXTRA_STYLESHEET = doxygen_html_style.css # The HTML_EXTRA_FILES tag can be used to specify one or more extra images or # other source files which should be copied to the HTML output directory. Note diff --git a/documents/doxygen/doxygen_html_style.css b/documents/doxygen/doxygen_html_style.css new file mode 100644 index 000000000..b4d993b73 --- /dev/null +++ b/documents/doxygen/doxygen_html_style.css @@ -0,0 +1,4 @@ +img[src="logo.png"]{ + width: 250px; + height: 83px; +} diff --git a/documents/doxygen/header b/documents/doxygen/header new file mode 100644 index 000000000..7fd1bd266 --- /dev/null +++ b/documents/doxygen/header @@ -0,0 +1,60 @@ + + + + + + + + +$projectname: $title +$title + + + + + + + + +$treeview +$search +$mathjax +$darkmode + +$extrastylesheet + + + + +
+ + + +
+ + +
+ + + + + + + + + + + + + + + + + + + + +
$searchbox
$searchbox
+
+ + diff --git a/documents/doxygen/logo.png b/documents/doxygen/logo.png index 58dd20081318fcd948611295d8a7cb44f0c17b3a..b9509d2e9b0f6536d8223e3518e2aff72715eb19 100644 GIT binary patch literal 71672 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 Ux`XEa!1!YDboFyt=akR{0EmETS^xk5 diff --git a/enclave/BUILD b/enclave/BUILD new file mode 100644 index 000000000..c802c97e1 --- /dev/null +++ b/enclave/BUILD @@ -0,0 +1,37 @@ +package(default_visibility = ["//visibility:public"]) + +filegroup( + name = "headers1", + srcs = glob(["*.h"]), +) + +cc_library( + name = "headers", + srcs = ["sgx_cpp_u.c"], + hdrs = [ + ":headers1", + "@openenclave//:headerfile" + ], + copts = ["-Iexternal/openenclave"], + linkopts = [ + "-lcrypto", + "-ldl", + "-lpthread", + ], + deps = [ + "@openenclave//:headers", + "@openenclave_host//:oehost_lib", + ], + visibility = ["//visibility:public"], +) + +cc_binary( + name = "oehost", + srcs = [ + "host.cpp", + ], + copts = ["-Iexternal/openenclave"], + deps = [ + ":headers", + ] +) diff --git a/enclave/sgx_cpp_args.h b/enclave/sgx_cpp_args.h new file mode 100644 index 000000000..1c533c194 --- /dev/null +++ b/enclave/sgx_cpp_args.h @@ -0,0 +1,160 @@ +/* + * This file is auto generated by oeedger8r. DO NOT EDIT. + */ +#ifndef EDGER8R_SGX_CPP_ARGS_H +#define EDGER8R_SGX_CPP_ARGS_H + +#include + +/**** User includes. ****/ +#include "openenclave/bits/edl/syscall_types.h" +#include "openenclave/corelibc/bits/types.h" +#include "openenclave/bits/types.h" +#include "openenclave/bits/sgx/sgxtypes.h" + +/**** User defined types in EDL. ****/ +#ifndef EDGER8R_STRUCT_OE_DIRENT +#define EDGER8R_STRUCT_OE_DIRENT +typedef struct oe_dirent +{ + uint64_t d_ino; + oe_off_t d_off; + uint16_t d_reclen; + uint8_t d_type; + char d_name[256]; +} oe_dirent; +#endif + +#ifndef EDGER8R_STRUCT___ST +#define EDGER8R_STRUCT___ST +typedef struct __st +{ + time_t tv_sec; + suseconds_t tv_nsec; +} __st; +#endif + +#ifndef EDGER8R_STRUCT_OE_STAT_T +#define EDGER8R_STRUCT_OE_STAT_T +typedef struct oe_stat_t +{ + oe_dev_t st_dev; + oe_ino_t st_ino; + oe_nlink_t st_nlink; + oe_mode_t st_mode; + oe_uid_t st_uid; + oe_gid_t st_gid; + uint32_t __st_pad0; + oe_dev_t st_rdev; + oe_off_t st_size; + oe_blksize_t st_blksize; + oe_blkcnt_t st_blocks; + struct __st st_atim; + struct __st st_mtim; + struct __st st_ctim; +} oe_stat_t; +#endif + +#ifndef EDGER8R_STRUCT_OE_HOST_POLLFD +#define EDGER8R_STRUCT_OE_HOST_POLLFD +typedef struct oe_host_pollfd +{ + oe_host_fd_t fd; + short int events; + short int revents; +} oe_host_pollfd; +#endif + +#ifndef EDGER8R_STRUCT_OE_POLLFD +#define EDGER8R_STRUCT_OE_POLLFD +typedef struct oe_pollfd +{ + int fd; + short int events; + short int revents; +} oe_pollfd; +#endif + +#ifndef EDGER8R_STRUCT_OE_SOCKADDR +#define EDGER8R_STRUCT_OE_SOCKADDR +typedef struct oe_sockaddr +{ + oe_sa_family_t sa_family; + char sa_data[14]; +} oe_sockaddr; +#endif + +#ifndef EDGER8R_STRUCT_OE_ADDRINFO +#define EDGER8R_STRUCT_OE_ADDRINFO +typedef struct oe_addrinfo +{ + int ai_flags; + int ai_family; + int ai_socktype; + int ai_protocol; + oe_socklen_t ai_addrlen; + struct oe_sockaddr* ai_addr; + char* ai_canonname; + struct oe_addrinfo* ai_next; +} oe_addrinfo; +#endif + +#ifndef EDGER8R_STRUCT_OE_TIMESPEC +#define EDGER8R_STRUCT_OE_TIMESPEC +typedef struct oe_timespec +{ + time_t tv_sec; + long int tv_nsec; +} oe_timespec; +#endif + +#ifndef EDGER8R_STRUCT_OE_UTSNAME +#define EDGER8R_STRUCT_OE_UTSNAME +typedef struct oe_utsname +{ + char sysname[65]; + char nodename[65]; + char release[65]; + char version[65]; + char machine[65]; + char domainname[65]; +} oe_utsname; +#endif + +#ifndef EDGER8R_STRUCT_FORMAT_IDS_T +#define EDGER8R_STRUCT_FORMAT_IDS_T +typedef struct format_ids_t +{ + void* data; + size_t size; +} format_ids_t; +#endif + +#ifndef EDGER8R_STRUCT_OE_HOST_WORKER_CONTEXT_T +#define EDGER8R_STRUCT_OE_HOST_WORKER_CONTEXT_T +typedef struct oe_host_worker_context_t +{ + void* call_arg; + oe_enclave_t* enc; + bool is_stopping; + int32_t event; + uint64_t spin_count; + uint64_t total_spin_count; +} oe_host_worker_context_t; +#endif + +#ifndef EDGER8R_STRUCT_OE_ENCLAVE_WORKER_CONTEXT_T +#define EDGER8R_STRUCT_OE_ENCLAVE_WORKER_CONTEXT_T +typedef struct oe_enclave_worker_context_t +{ + void* call_arg; + oe_enclave_t* enc; + bool is_stopping; + int32_t event; + uint64_t spin_count; + uint64_t spin_count_threshold; + uint64_t total_spin_count; +} oe_enclave_worker_context_t; +#endif + +#endif // EDGER8R_SGX_CPP_ARGS_H diff --git a/enclave/sgx_cpp_u.c b/enclave/sgx_cpp_u.c new file mode 100644 index 000000000..b1dac9510 --- /dev/null +++ b/enclave/sgx_cpp_u.c @@ -0,0 +1,7997 @@ +/* + * This file is auto generated by oeedger8r. DO NOT EDIT. + */ +#include "sgx_cpp_u.h" + +#include + +OE_EXTERNC_BEGIN + +/**** Trusted function IDs. ****/ +enum +{ + sgx_cpp_fcn_id_request_counter = 0, + sgx_cpp_fcn_id_get_counter = 1, + sgx_cpp_fcn_id_reset_prng = 2, + sgx_cpp_fcn_id_generate_rdrand = 3, + sgx_cpp_fcn_id_generate_rand = 4, + sgx_cpp_fcn_id_generate_key = 5, + sgx_cpp_fcn_id_update_key = 6, + sgx_cpp_fcn_id_get_pubkey = 7, + sgx_cpp_fcn_id_encrypt = 8, + sgx_cpp_fcn_id_decrypt = 9, + sgx_cpp_fcn_id_oe_get_sgx_report_ecall = 10, + sgx_cpp_fcn_id_oe_get_report_v2_ecall = 11, + sgx_cpp_fcn_id_oe_verify_local_report_ecall = 12, + sgx_cpp_fcn_id_oe_sgx_init_context_switchless_ecall = 13, + sgx_cpp_fcn_id_oe_sgx_switchless_enclave_worker_thread_ecall = 14, + sgx_cpp_fcn_id_trusted_call_id_max = OE_ENUM_MAX +}; + +/**** Trusted function names. ****/ +static const oe_ecall_info_t _sgx_cpp_ecall_info_table[] = +{ + { "request_counter" }, + { "get_counter" }, + { "reset_prng" }, + { "generate_rdrand" }, + { "generate_rand" }, + { "generate_key" }, + { "update_key" }, + { "get_pubkey" }, + { "encrypt" }, + { "decrypt" }, + { "oe_get_sgx_report_ecall" }, + { "oe_get_report_v2_ecall" }, + { "oe_verify_local_report_ecall" }, + { "oe_sgx_init_context_switchless_ecall" }, + { "oe_sgx_switchless_enclave_worker_thread_ecall" }, +}; + +/**** ECALL marshalling structs. ****/ +typedef struct _request_counter_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint32_t* index; +} request_counter_args_t; + +typedef struct _get_counter_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint32_t* index; + size_t previous_size; + size_t limit_count; + uint32_t* counter_value_array; + size_t* buffer_size_array; + unsigned char** previous_attestation; + uint32_t* counter_value; +} get_counter_args_t; + +typedef struct _reset_prng_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint32_t* seed; + uint32_t* range; +} reset_prng_args_t; + +typedef struct _generate_rdrand_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint32_t* rdrandNum; +} generate_rdrand_args_t; + +typedef struct _generate_rand_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + size_t input_len; + size_t limit_count; + uint32_t* counter_value_array; + size_t* buffer_size_array; + unsigned char** previous_attestation; + uint32_t* randNum; +} generate_rand_args_t; + +typedef struct _generate_key_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + size_t* key_size; +} generate_key_args_t; + +typedef struct _update_key_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + unsigned char* priv_key; + size_t priv_len; + unsigned char* pub_key; + size_t pub_len; +} update_key_args_t; + +typedef struct _get_pubkey_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + unsigned char** key_buf; + size_t* key_len; +} get_pubkey_args_t; + +typedef struct _encrypt_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + unsigned char* input_buf; + unsigned char** output_buf; + size_t input_len; + size_t* output_len; +} encrypt_args_t; + +typedef struct _decrypt_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + unsigned char* input_buf; + unsigned char** output_buf; + size_t input_len; + size_t* output_len; +} decrypt_args_t; + +typedef struct _oe_get_sgx_report_ecall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + void* opt_params; + size_t opt_params_size; + sgx_report_t* report; +} oe_get_sgx_report_ecall_args_t; + +typedef struct _oe_get_report_v2_ecall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + uint32_t flags; + void* opt_params; + size_t opt_params_size; + uint8_t** report_buffer; + size_t* report_buffer_size; +} oe_get_report_v2_ecall_args_t; + +typedef struct _oe_verify_local_report_ecall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + uint8_t* report; + size_t report_size; + oe_report_t* parsed_report; +} oe_verify_local_report_ecall_args_t; + +typedef struct _oe_sgx_init_context_switchless_ecall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + oe_host_worker_context_t* host_worker_contexts; + uint64_t num_host_workers; +} oe_sgx_init_context_switchless_ecall_args_t; + +typedef struct _oe_sgx_switchless_enclave_worker_thread_ecall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_enclave_worker_context_t* context; +} oe_sgx_switchless_enclave_worker_thread_ecall_args_t; + +/**** ECALL function wrappers. ****/ + +oe_result_t sgx_cpp_request_counter( + oe_enclave_t* enclave, + int* _retval, + uint32_t* index) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + request_counter_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.index = (uint32_t*)index; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(request_counter_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(request_counter_args_t)); + if (index) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(uint32_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (request_counter_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_request_counter].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (request_counter_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(index, 1, sizeof(uint32_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_request_counter, request_counter); + +oe_result_t sgx_cpp_get_counter( + oe_enclave_t* enclave, + int* _retval, + uint32_t* index, + size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t* buffer_size_array, + unsigned char** previous_attestation, + uint32_t* counter_value) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + get_counter_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.index = (uint32_t*)index; + _args.previous_size = previous_size; + _args.limit_count = limit_count; + _args.counter_value_array = (uint32_t*)counter_value_array; + _args.buffer_size_array = (size_t*)buffer_size_array; + _args.previous_attestation = (unsigned char**)previous_attestation; + _args.counter_value = (uint32_t*)counter_value; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(get_counter_args_t)); + if (index) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(uint32_t)); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(get_counter_args_t)); + if (counter_value) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(uint32_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (get_counter_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (index) + OE_WRITE_IN_PARAM(index, 1, sizeof(uint32_t), uint32_t*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_get_counter].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (get_counter_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(counter_value, 1, sizeof(uint32_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_get_counter, get_counter); + +oe_result_t sgx_cpp_reset_prng( + oe_enclave_t* enclave, + int* _retval, + uint32_t* seed, + uint32_t* range) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + reset_prng_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.seed = (uint32_t*)seed; + _args.range = (uint32_t*)range; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(reset_prng_args_t)); + if (seed) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(uint32_t)); + if (range) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(uint32_t)); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(reset_prng_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (reset_prng_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (seed) + OE_WRITE_IN_PARAM(seed, 1, sizeof(uint32_t), uint32_t*); + if (range) + OE_WRITE_IN_PARAM(range, 1, sizeof(uint32_t), uint32_t*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_reset_prng].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (reset_prng_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_reset_prng, reset_prng); + +oe_result_t sgx_cpp_generate_rdrand( + oe_enclave_t* enclave, + int* _retval, + uint32_t* rdrandNum) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + generate_rdrand_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.rdrandNum = (uint32_t*)rdrandNum; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(generate_rdrand_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(generate_rdrand_args_t)); + if (rdrandNum) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(uint32_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (generate_rdrand_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_generate_rdrand].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (generate_rdrand_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(rdrandNum, 1, sizeof(uint32_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_generate_rdrand, generate_rdrand); + +oe_result_t sgx_cpp_generate_rand( + oe_enclave_t* enclave, + int* _retval, + size_t input_len, + size_t limit_count, + uint32_t* counter_value_array, + size_t* buffer_size_array, + unsigned char** previous_attestation, + uint32_t* randNum) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + generate_rand_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.input_len = input_len; + _args.limit_count = limit_count; + _args.counter_value_array = (uint32_t*)counter_value_array; + _args.buffer_size_array = (size_t*)buffer_size_array; + _args.previous_attestation = (unsigned char**)previous_attestation; + _args.randNum = (uint32_t*)randNum; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(generate_rand_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(generate_rand_args_t)); + if (randNum) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(uint32_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (generate_rand_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_generate_rand].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (generate_rand_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(randNum, 1, sizeof(uint32_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_generate_rand, generate_rand); + +oe_result_t sgx_cpp_generate_key( + oe_enclave_t* enclave, + int* _retval, + size_t* key_size) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + generate_key_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.key_size = (size_t*)key_size; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(generate_key_args_t)); + if (key_size) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(size_t)); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(generate_key_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (generate_key_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (key_size) + OE_WRITE_IN_PARAM(key_size, 1, sizeof(size_t), size_t*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_generate_key].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (generate_key_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_generate_key, generate_key); + +oe_result_t sgx_cpp_update_key( + oe_enclave_t* enclave, + int* _retval, + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + update_key_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.priv_key = (unsigned char*)priv_key; + _args.priv_len = priv_len; + _args.pub_key = (unsigned char*)pub_key; + _args.pub_len = pub_len; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(update_key_args_t)); + if (priv_key) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(unsigned char)); + if (pub_key) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, sizeof(unsigned char)); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(update_key_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (update_key_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (priv_key) + OE_WRITE_IN_PARAM(priv_key, 1, sizeof(unsigned char), unsigned char*); + if (pub_key) + OE_WRITE_IN_PARAM(pub_key, 1, sizeof(unsigned char), unsigned char*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_update_key].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (update_key_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_update_key, update_key); + +oe_result_t sgx_cpp_get_pubkey( + oe_enclave_t* enclave, + int* _retval, + unsigned char** key_buf, + size_t* key_len) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + get_pubkey_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.key_buf = (unsigned char**)key_buf; + _args.key_len = (size_t*)key_len; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(get_pubkey_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(get_pubkey_args_t)); + if (key_len) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(size_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (get_pubkey_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_get_pubkey].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (get_pubkey_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(key_len, 1, sizeof(size_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_get_pubkey, get_pubkey); + +oe_result_t sgx_cpp_encrypt( + oe_enclave_t* enclave, + int* _retval, + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + encrypt_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.input_buf = (unsigned char*)input_buf; + _args.output_buf = (unsigned char**)output_buf; + _args.input_len = input_len; + _args.output_len = output_len; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(encrypt_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(encrypt_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (encrypt_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_encrypt].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (encrypt_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_encrypt, encrypt); + +oe_result_t sgx_cpp_decrypt( + oe_enclave_t* enclave, + int* _retval, + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + decrypt_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.input_buf = (unsigned char*)input_buf; + _args.output_buf = (unsigned char**)output_buf; + _args.input_len = input_len; + _args.output_len = output_len; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(decrypt_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(decrypt_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (decrypt_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_decrypt].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (decrypt_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_decrypt, decrypt); + +oe_result_t sgx_cpp_oe_get_sgx_report_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + const void* opt_params, + size_t opt_params_size, + sgx_report_t* report) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + oe_get_sgx_report_ecall_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.opt_params = (void*)opt_params; + _args.opt_params_size = opt_params_size; + _args.report = (sgx_report_t*)report; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(oe_get_sgx_report_ecall_args_t)); + if (opt_params) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, _args.opt_params_size); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(oe_get_sgx_report_ecall_args_t)); + if (report) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(sgx_report_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (oe_get_sgx_report_ecall_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (opt_params) + OE_WRITE_IN_PARAM(opt_params, 1, _args.opt_params_size, void*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_oe_get_sgx_report_ecall].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (oe_get_sgx_report_ecall_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(report, 1, sizeof(sgx_report_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_oe_get_sgx_report_ecall, oe_get_sgx_report_ecall); + +oe_result_t sgx_cpp_oe_get_report_v2_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + uint32_t flags, + const void* opt_params, + size_t opt_params_size, + uint8_t** report_buffer, + size_t* report_buffer_size) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + oe_get_report_v2_ecall_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.flags = flags; + _args.opt_params = (void*)opt_params; + _args.opt_params_size = opt_params_size; + _args.report_buffer = (uint8_t**)report_buffer; + _args.report_buffer_size = (size_t*)report_buffer_size; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(oe_get_report_v2_ecall_args_t)); + if (opt_params) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, _args.opt_params_size); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(oe_get_report_v2_ecall_args_t)); + if (report_buffer) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(uint8_t*)); + if (report_buffer_size) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(size_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (oe_get_report_v2_ecall_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (opt_params) + OE_WRITE_IN_PARAM(opt_params, 1, _args.opt_params_size, void*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_oe_get_report_v2_ecall].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (oe_get_report_v2_ecall_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(report_buffer, 1, sizeof(uint8_t*)); + OE_READ_OUT_PARAM(report_buffer_size, 1, sizeof(size_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_oe_get_report_v2_ecall, oe_get_report_v2_ecall); + +oe_result_t sgx_cpp_oe_verify_local_report_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + const uint8_t* report, + size_t report_size, + oe_report_t* parsed_report) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + oe_verify_local_report_ecall_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.report = (uint8_t*)report; + _args.report_size = report_size; + _args.parsed_report = (oe_report_t*)parsed_report; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(oe_verify_local_report_ecall_args_t)); + if (report) + OE_ADD_ARG_SIZE(_input_buffer_size, 1, _args.report_size); + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(oe_verify_local_report_ecall_args_t)); + if (parsed_report) + OE_ADD_ARG_SIZE(_output_buffer_size, 1, sizeof(oe_report_t)); + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (oe_verify_local_report_ecall_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + if (report) + OE_WRITE_IN_PARAM(report, 1, _args.report_size, uint8_t*); + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_oe_verify_local_report_ecall].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (oe_verify_local_report_ecall_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + OE_READ_OUT_PARAM(parsed_report, 1, sizeof(oe_report_t)); + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_oe_verify_local_report_ecall, oe_verify_local_report_ecall); + +oe_result_t sgx_cpp_oe_sgx_init_context_switchless_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + oe_host_worker_context_t* host_worker_contexts, + uint64_t num_host_workers) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + oe_sgx_init_context_switchless_ecall_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.host_worker_contexts = (oe_host_worker_context_t*)host_worker_contexts; + _args.num_host_workers = num_host_workers; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(oe_sgx_init_context_switchless_ecall_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(oe_sgx_init_context_switchless_ecall_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (oe_sgx_init_context_switchless_ecall_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_oe_sgx_init_context_switchless_ecall].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (oe_sgx_init_context_switchless_ecall_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + *_retval = _pargs_out->oe_retval; + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_oe_sgx_init_context_switchless_ecall, oe_sgx_init_context_switchless_ecall); + +oe_result_t sgx_cpp_oe_sgx_switchless_enclave_worker_thread_ecall( + oe_enclave_t* enclave, + oe_enclave_worker_context_t* context) +{ + oe_result_t _result = OE_FAILURE; + + static uint64_t global_id = OE_GLOBAL_ECALL_ID_NULL; + + /* Marshalling struct. */ + oe_sgx_switchless_enclave_worker_thread_ecall_args_t _args, *_pargs_in = NULL, *_pargs_out = NULL; + /* Marshalling buffer and sizes. */ + size_t _input_buffer_size = 0; + size_t _output_buffer_size = 0; + size_t _total_buffer_size = 0; + uint8_t* _buffer = NULL; + uint8_t* _input_buffer = NULL; + uint8_t* _output_buffer = NULL; + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + size_t _output_bytes_written = 0; + + /* Fill marshalling struct. */ + memset(&_args, 0, sizeof(_args)); + _args.context = (oe_enclave_worker_context_t*)context; + + /* Compute input buffer size. Include in and in-out parameters. */ + OE_ADD_SIZE(_input_buffer_size, sizeof(oe_sgx_switchless_enclave_worker_thread_ecall_args_t)); + /* There were no corresponding parameters. */ + + /* Compute output buffer size. Include out and in-out parameters. */ + OE_ADD_SIZE(_output_buffer_size, sizeof(oe_sgx_switchless_enclave_worker_thread_ecall_args_t)); + /* There were no corresponding parameters. */ + + /* Allocate marshalling buffer. */ + _total_buffer_size = _input_buffer_size; + OE_ADD_SIZE(_total_buffer_size, _output_buffer_size); + _buffer = (uint8_t*)oe_malloc(_total_buffer_size); + _input_buffer = _buffer; + _output_buffer = _buffer + _input_buffer_size; + if (_buffer == NULL) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + + /* Serialize buffer inputs (in and in-out parameters). */ + _pargs_in = (oe_sgx_switchless_enclave_worker_thread_ecall_args_t*)_input_buffer; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + /* There were no in nor in-out parameters. */ + + /* Copy args structure (now filled) to input buffer. */ + memcpy(_pargs_in, &_args, sizeof(*_pargs_in)); + + /* Call enclave function. */ + if ((_result = oe_call_enclave_function( + enclave, + &global_id, + _sgx_cpp_ecall_info_table[sgx_cpp_fcn_id_oe_sgx_switchless_enclave_worker_thread_ecall].name, + _input_buffer, + _input_buffer_size, + _output_buffer, + _output_buffer_size, + &_output_bytes_written)) != OE_OK) + goto done; + + /* Setup output arg struct pointer. */ + _pargs_out = (oe_sgx_switchless_enclave_worker_thread_ecall_args_t*)_output_buffer; + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + /* Check if the call succeeded. */ + if ((_result = _pargs_out->oe_result) != OE_OK) + goto done; + + /* Currently exactly _output_buffer_size bytes must be written. */ + if (_output_bytes_written != _output_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Unmarshal return value and out, in-out parameters. */ + /* No return value. */ + + /* There were no out nor in-out parameters. */ + + _result = OE_OK; + +done: + if (_buffer) + oe_free(_buffer); + + return _result; +} + +OE_WEAK_ALIAS(sgx_cpp_oe_sgx_switchless_enclave_worker_thread_ecall, oe_sgx_switchless_enclave_worker_thread_ecall); + +/**** Untrusted function IDs. ****/ +enum +{ + sgx_cpp_fcn_id_oe_syscall_epoll_create1_ocall = 0, + sgx_cpp_fcn_id_oe_syscall_epoll_wait_ocall = 1, + sgx_cpp_fcn_id_oe_syscall_epoll_wake_ocall = 2, + sgx_cpp_fcn_id_oe_syscall_epoll_ctl_ocall = 3, + sgx_cpp_fcn_id_oe_syscall_epoll_close_ocall = 4, + sgx_cpp_fcn_id_oe_syscall_open_ocall = 5, + sgx_cpp_fcn_id_oe_syscall_read_ocall = 6, + sgx_cpp_fcn_id_oe_syscall_write_ocall = 7, + sgx_cpp_fcn_id_oe_syscall_readv_ocall = 8, + sgx_cpp_fcn_id_oe_syscall_writev_ocall = 9, + sgx_cpp_fcn_id_oe_syscall_lseek_ocall = 10, + sgx_cpp_fcn_id_oe_syscall_pread_ocall = 11, + sgx_cpp_fcn_id_oe_syscall_pwrite_ocall = 12, + sgx_cpp_fcn_id_oe_syscall_close_ocall = 13, + sgx_cpp_fcn_id_oe_syscall_flock_ocall = 14, + sgx_cpp_fcn_id_oe_syscall_fsync_ocall = 15, + sgx_cpp_fcn_id_oe_syscall_fdatasync_ocall = 16, + sgx_cpp_fcn_id_oe_syscall_dup_ocall = 17, + sgx_cpp_fcn_id_oe_syscall_opendir_ocall = 18, + sgx_cpp_fcn_id_oe_syscall_readdir_ocall = 19, + sgx_cpp_fcn_id_oe_syscall_rewinddir_ocall = 20, + sgx_cpp_fcn_id_oe_syscall_closedir_ocall = 21, + sgx_cpp_fcn_id_oe_syscall_stat_ocall = 22, + sgx_cpp_fcn_id_oe_syscall_fstat_ocall = 23, + sgx_cpp_fcn_id_oe_syscall_access_ocall = 24, + sgx_cpp_fcn_id_oe_syscall_link_ocall = 25, + sgx_cpp_fcn_id_oe_syscall_unlink_ocall = 26, + sgx_cpp_fcn_id_oe_syscall_rename_ocall = 27, + sgx_cpp_fcn_id_oe_syscall_truncate_ocall = 28, + sgx_cpp_fcn_id_oe_syscall_ftruncate_ocall = 29, + sgx_cpp_fcn_id_oe_syscall_mkdir_ocall = 30, + sgx_cpp_fcn_id_oe_syscall_rmdir_ocall = 31, + sgx_cpp_fcn_id_oe_syscall_fcntl_ocall = 32, + sgx_cpp_fcn_id_oe_syscall_ioctl_ocall = 33, + sgx_cpp_fcn_id_oe_syscall_poll_ocall = 34, + sgx_cpp_fcn_id_oe_syscall_kill_ocall = 35, + sgx_cpp_fcn_id_oe_syscall_close_socket_ocall = 36, + sgx_cpp_fcn_id_oe_syscall_socket_ocall = 37, + sgx_cpp_fcn_id_oe_syscall_shutdown_sockets_device_ocall = 38, + sgx_cpp_fcn_id_oe_syscall_socketpair_ocall = 39, + sgx_cpp_fcn_id_oe_syscall_connect_ocall = 40, + sgx_cpp_fcn_id_oe_syscall_accept_ocall = 41, + sgx_cpp_fcn_id_oe_syscall_bind_ocall = 42, + sgx_cpp_fcn_id_oe_syscall_listen_ocall = 43, + sgx_cpp_fcn_id_oe_syscall_recvmsg_ocall = 44, + sgx_cpp_fcn_id_oe_syscall_sendmsg_ocall = 45, + sgx_cpp_fcn_id_oe_syscall_recv_ocall = 46, + sgx_cpp_fcn_id_oe_syscall_recvfrom_ocall = 47, + sgx_cpp_fcn_id_oe_syscall_send_ocall = 48, + sgx_cpp_fcn_id_oe_syscall_sendto_ocall = 49, + sgx_cpp_fcn_id_oe_syscall_recvv_ocall = 50, + sgx_cpp_fcn_id_oe_syscall_sendv_ocall = 51, + sgx_cpp_fcn_id_oe_syscall_shutdown_ocall = 52, + sgx_cpp_fcn_id_oe_syscall_setsockopt_ocall = 53, + sgx_cpp_fcn_id_oe_syscall_getsockopt_ocall = 54, + sgx_cpp_fcn_id_oe_syscall_getsockname_ocall = 55, + sgx_cpp_fcn_id_oe_syscall_getpeername_ocall = 56, + sgx_cpp_fcn_id_oe_syscall_getaddrinfo_open_ocall = 57, + sgx_cpp_fcn_id_oe_syscall_getaddrinfo_read_ocall = 58, + sgx_cpp_fcn_id_oe_syscall_getaddrinfo_close_ocall = 59, + sgx_cpp_fcn_id_oe_syscall_getnameinfo_ocall = 60, + sgx_cpp_fcn_id_oe_syscall_nanosleep_ocall = 61, + sgx_cpp_fcn_id_oe_syscall_clock_nanosleep_ocall = 62, + sgx_cpp_fcn_id_oe_syscall_getpid_ocall = 63, + sgx_cpp_fcn_id_oe_syscall_getppid_ocall = 64, + sgx_cpp_fcn_id_oe_syscall_getpgrp_ocall = 65, + sgx_cpp_fcn_id_oe_syscall_getuid_ocall = 66, + sgx_cpp_fcn_id_oe_syscall_geteuid_ocall = 67, + sgx_cpp_fcn_id_oe_syscall_getgid_ocall = 68, + sgx_cpp_fcn_id_oe_syscall_getegid_ocall = 69, + sgx_cpp_fcn_id_oe_syscall_getpgid_ocall = 70, + sgx_cpp_fcn_id_oe_syscall_getgroups_ocall = 71, + sgx_cpp_fcn_id_oe_syscall_uname_ocall = 72, + sgx_cpp_fcn_id_oe_get_supported_attester_format_ids_ocall = 73, + sgx_cpp_fcn_id_oe_get_qetarget_info_ocall = 74, + sgx_cpp_fcn_id_oe_get_quote_ocall = 75, + sgx_cpp_fcn_id_oe_get_quote_verification_collateral_ocall = 76, + sgx_cpp_fcn_id_oe_verify_quote_ocall = 77, + sgx_cpp_fcn_id_oe_sgx_get_cpuid_table_ocall = 78, + sgx_cpp_fcn_id_oe_sgx_backtrace_symbols_ocall = 79, + sgx_cpp_fcn_id_oe_sgx_thread_wake_wait_ocall = 80, + sgx_cpp_fcn_id_oe_sgx_wake_switchless_worker_ocall = 81, + sgx_cpp_fcn_id_oe_sgx_sleep_switchless_worker_ocall = 82, + sgx_cpp_fcn_id_untrusted_call_max = OE_ENUM_MAX +}; + +/**** OCALL marshalling structs. ****/ +typedef struct _oe_syscall_epoll_create1_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_fd_t oe_retval; + int flags; + int ocall_errno; +} oe_syscall_epoll_create1_ocall_args_t; + +typedef struct _oe_syscall_epoll_wait_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int64_t epfd; + struct oe_epoll_event* events; + unsigned int maxevents; + int timeout; + int ocall_errno; +} oe_syscall_epoll_wait_ocall_args_t; + +typedef struct _oe_syscall_epoll_wake_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int ocall_errno; +} oe_syscall_epoll_wake_ocall_args_t; + +typedef struct _oe_syscall_epoll_ctl_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int64_t epfd; + int op; + int64_t fd; + struct oe_epoll_event* event; + int ocall_errno; +} oe_syscall_epoll_ctl_ocall_args_t; + +typedef struct _oe_syscall_epoll_close_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t epfd; + int ocall_errno; +} oe_syscall_epoll_close_ocall_args_t; + +typedef struct _oe_syscall_open_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_fd_t oe_retval; + char* pathname; + size_t pathname_len; + int flags; + oe_mode_t mode; + int ocall_errno; +} oe_syscall_open_ocall_args_t; + +typedef struct _oe_syscall_read_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* buf; + size_t count; + int ocall_errno; +} oe_syscall_read_ocall_args_t; + +typedef struct _oe_syscall_write_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* buf; + size_t count; + int ocall_errno; +} oe_syscall_write_ocall_args_t; + +typedef struct _oe_syscall_readv_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* iov_buf; + int iovcnt; + size_t iov_buf_size; + int ocall_errno; +} oe_syscall_readv_ocall_args_t; + +typedef struct _oe_syscall_writev_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* iov_buf; + int iovcnt; + size_t iov_buf_size; + int ocall_errno; +} oe_syscall_writev_ocall_args_t; + +typedef struct _oe_syscall_lseek_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_off_t oe_retval; + oe_host_fd_t fd; + oe_off_t offset; + int whence; + int ocall_errno; +} oe_syscall_lseek_ocall_args_t; + +typedef struct _oe_syscall_pread_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* buf; + size_t count; + oe_off_t offset; + int ocall_errno; +} oe_syscall_pread_ocall_args_t; + +typedef struct _oe_syscall_pwrite_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* buf; + size_t count; + oe_off_t offset; + int ocall_errno; +} oe_syscall_pwrite_ocall_args_t; + +typedef struct _oe_syscall_close_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + int ocall_errno; +} oe_syscall_close_ocall_args_t; + +typedef struct _oe_syscall_flock_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + int operation; + int ocall_errno; +} oe_syscall_flock_ocall_args_t; + +typedef struct _oe_syscall_fsync_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + int ocall_errno; +} oe_syscall_fsync_ocall_args_t; + +typedef struct _oe_syscall_fdatasync_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + int ocall_errno; +} oe_syscall_fdatasync_ocall_args_t; + +typedef struct _oe_syscall_dup_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_fd_t oe_retval; + oe_host_fd_t oldfd; + int ocall_errno; +} oe_syscall_dup_ocall_args_t; + +typedef struct _oe_syscall_opendir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + uint64_t oe_retval; + char* name; + size_t name_len; + int ocall_errno; +} oe_syscall_opendir_ocall_args_t; + +typedef struct _oe_syscall_readdir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint64_t dirp; + struct oe_dirent* entry; + int ocall_errno; +} oe_syscall_readdir_ocall_args_t; + +typedef struct _oe_syscall_rewinddir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + uint64_t dirp; +} oe_syscall_rewinddir_ocall_args_t; + +typedef struct _oe_syscall_closedir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint64_t dirp; + int ocall_errno; +} oe_syscall_closedir_ocall_args_t; + +typedef struct _oe_syscall_stat_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* pathname; + size_t pathname_len; + struct oe_stat_t* buf; + int ocall_errno; +} oe_syscall_stat_ocall_args_t; + +typedef struct _oe_syscall_fstat_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + struct oe_stat_t* buf; + int ocall_errno; +} oe_syscall_fstat_ocall_args_t; + +typedef struct _oe_syscall_access_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* pathname; + size_t pathname_len; + int mode; + int ocall_errno; +} oe_syscall_access_ocall_args_t; + +typedef struct _oe_syscall_link_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* oldpath; + size_t oldpath_len; + char* newpath; + size_t newpath_len; + int ocall_errno; +} oe_syscall_link_ocall_args_t; + +typedef struct _oe_syscall_unlink_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* pathname; + size_t pathname_len; + int ocall_errno; +} oe_syscall_unlink_ocall_args_t; + +typedef struct _oe_syscall_rename_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* oldpath; + size_t oldpath_len; + char* newpath; + size_t newpath_len; + int ocall_errno; +} oe_syscall_rename_ocall_args_t; + +typedef struct _oe_syscall_truncate_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* path; + size_t path_len; + oe_off_t length; + int ocall_errno; +} oe_syscall_truncate_ocall_args_t; + +typedef struct _oe_syscall_ftruncate_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + oe_off_t length; + int ocall_errno; +} oe_syscall_ftruncate_ocall_args_t; + +typedef struct _oe_syscall_mkdir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* pathname; + size_t pathname_len; + oe_mode_t mode; + int ocall_errno; +} oe_syscall_mkdir_ocall_args_t; + +typedef struct _oe_syscall_rmdir_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* pathname; + size_t pathname_len; + int ocall_errno; +} oe_syscall_rmdir_ocall_args_t; + +typedef struct _oe_syscall_fcntl_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + int cmd; + uint64_t arg; + uint64_t argsize; + void* argout; + int ocall_errno; +} oe_syscall_fcntl_ocall_args_t; + +typedef struct _oe_syscall_ioctl_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t fd; + uint64_t request; + uint64_t arg; + uint64_t argsize; + void* argout; + int ocall_errno; +} oe_syscall_ioctl_ocall_args_t; + +typedef struct _oe_syscall_poll_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + struct oe_host_pollfd* host_fds; + oe_nfds_t nfds; + int timeout; + int ocall_errno; +} oe_syscall_poll_ocall_args_t; + +typedef struct _oe_syscall_kill_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int pid; + int signum; + int ocall_errno; +} oe_syscall_kill_ocall_args_t; + +typedef struct _oe_syscall_close_socket_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int ocall_errno; +} oe_syscall_close_socket_ocall_args_t; + +typedef struct _oe_syscall_socket_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_fd_t oe_retval; + int domain; + int type; + int protocol; + int ocall_errno; +} oe_syscall_socket_ocall_args_t; + +typedef struct _oe_syscall_shutdown_sockets_device_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int ocall_errno; +} oe_syscall_shutdown_sockets_device_ocall_args_t; + +typedef struct _oe_syscall_socketpair_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int domain; + int type; + int protocol; + oe_host_fd_t* sv; + int ocall_errno; +} oe_syscall_socketpair_ocall_args_t; + +typedef struct _oe_syscall_connect_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + struct oe_sockaddr* addr; + oe_socklen_t addrlen; + int ocall_errno; +} oe_syscall_connect_ocall_args_t; + +typedef struct _oe_syscall_accept_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_fd_t oe_retval; + oe_host_fd_t sockfd; + struct oe_sockaddr* addr; + oe_socklen_t addrlen_in; + oe_socklen_t* addrlen_out; + int ocall_errno; +} oe_syscall_accept_ocall_args_t; + +typedef struct _oe_syscall_bind_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + struct oe_sockaddr* addr; + oe_socklen_t addrlen; + int ocall_errno; +} oe_syscall_bind_ocall_args_t; + +typedef struct _oe_syscall_listen_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int backlog; + int ocall_errno; +} oe_syscall_listen_ocall_args_t; + +typedef struct _oe_syscall_recvmsg_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* msg_name; + oe_socklen_t msg_namelen; + oe_socklen_t* msg_namelen_out; + void* msg_iov_buf; + size_t msg_iovlen; + size_t msg_iov_buf_size; + void* msg_control; + size_t msg_controllen; + size_t* msg_controllen_out; + int flags; + int ocall_errno; +} oe_syscall_recvmsg_ocall_args_t; + +typedef struct _oe_syscall_sendmsg_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* msg_name; + oe_socklen_t msg_namelen; + void* msg_iov_buf; + size_t msg_iovlen; + size_t msg_iov_buf_size; + void* msg_control; + size_t msg_controllen; + int flags; + int ocall_errno; +} oe_syscall_sendmsg_ocall_args_t; + +typedef struct _oe_syscall_recv_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* buf; + size_t len; + int flags; + int ocall_errno; +} oe_syscall_recv_ocall_args_t; + +typedef struct _oe_syscall_recvfrom_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* buf; + size_t len; + int flags; + struct oe_sockaddr* src_addr; + oe_socklen_t addrlen_in; + oe_socklen_t* addrlen_out; + int ocall_errno; +} oe_syscall_recvfrom_ocall_args_t; + +typedef struct _oe_syscall_send_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* buf; + size_t len; + int flags; + int ocall_errno; +} oe_syscall_send_ocall_args_t; + +typedef struct _oe_syscall_sendto_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t sockfd; + void* buf; + size_t len; + int flags; + struct oe_sockaddr* dest_addr; + oe_socklen_t addrlen; + int ocall_errno; +} oe_syscall_sendto_ocall_args_t; + +typedef struct _oe_syscall_recvv_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* iov_buf; + int iovcnt; + size_t iov_buf_size; + int ocall_errno; +} oe_syscall_recvv_ocall_args_t; + +typedef struct _oe_syscall_sendv_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + ssize_t oe_retval; + oe_host_fd_t fd; + void* iov_buf; + int iovcnt; + size_t iov_buf_size; + int ocall_errno; +} oe_syscall_sendv_ocall_args_t; + +typedef struct _oe_syscall_shutdown_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int how; + int ocall_errno; +} oe_syscall_shutdown_ocall_args_t; + +typedef struct _oe_syscall_setsockopt_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int level; + int optname; + void* optval; + oe_socklen_t optlen; + int ocall_errno; +} oe_syscall_setsockopt_ocall_args_t; + +typedef struct _oe_syscall_getsockopt_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + int level; + int optname; + void* optval; + oe_socklen_t optlen_in; + oe_socklen_t* optlen_out; + int ocall_errno; +} oe_syscall_getsockopt_ocall_args_t; + +typedef struct _oe_syscall_getsockname_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + struct oe_sockaddr* addr; + oe_socklen_t addrlen_in; + oe_socklen_t* addrlen_out; + int ocall_errno; +} oe_syscall_getsockname_ocall_args_t; + +typedef struct _oe_syscall_getpeername_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_host_fd_t sockfd; + struct oe_sockaddr* addr; + oe_socklen_t addrlen_in; + oe_socklen_t* addrlen_out; + int ocall_errno; +} oe_syscall_getpeername_ocall_args_t; + +typedef struct _oe_syscall_getaddrinfo_open_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + char* node; + size_t node_len; + char* service; + size_t service_len; + struct oe_addrinfo* hints; + uint64_t* handle; + int ocall_errno; +} oe_syscall_getaddrinfo_open_ocall_args_t; + +typedef struct _oe_syscall_getaddrinfo_read_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint64_t handle; + int* ai_flags; + int* ai_family; + int* ai_socktype; + int* ai_protocol; + oe_socklen_t ai_addrlen_in; + oe_socklen_t* ai_addrlen; + struct oe_sockaddr* ai_addr; + size_t ai_canonnamelen_in; + size_t* ai_canonnamelen; + char* ai_canonname; + int ocall_errno; +} oe_syscall_getaddrinfo_read_ocall_args_t; + +typedef struct _oe_syscall_getaddrinfo_close_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + uint64_t handle; + int ocall_errno; +} oe_syscall_getaddrinfo_close_ocall_args_t; + +typedef struct _oe_syscall_getnameinfo_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + struct oe_sockaddr* sa; + oe_socklen_t salen; + char* host; + oe_socklen_t hostlen; + char* serv; + oe_socklen_t servlen; + int flags; + int ocall_errno; +} oe_syscall_getnameinfo_ocall_args_t; + +typedef struct _oe_syscall_nanosleep_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + struct oe_timespec* req; + struct oe_timespec* rem; + int ocall_errno; +} oe_syscall_nanosleep_ocall_args_t; + +typedef struct _oe_syscall_clock_nanosleep_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + oe_clockid_t clockid; + int flag; + struct oe_timespec* req; + struct oe_timespec* rem; + int ocall_errno; +} oe_syscall_clock_nanosleep_ocall_args_t; + +typedef struct _oe_syscall_getpid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; +} oe_syscall_getpid_ocall_args_t; + +typedef struct _oe_syscall_getppid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; +} oe_syscall_getppid_ocall_args_t; + +typedef struct _oe_syscall_getpgrp_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; +} oe_syscall_getpgrp_ocall_args_t; + +typedef struct _oe_syscall_getuid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + unsigned int oe_retval; +} oe_syscall_getuid_ocall_args_t; + +typedef struct _oe_syscall_geteuid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + unsigned int oe_retval; +} oe_syscall_geteuid_ocall_args_t; + +typedef struct _oe_syscall_getgid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + unsigned int oe_retval; +} oe_syscall_getgid_ocall_args_t; + +typedef struct _oe_syscall_getegid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + unsigned int oe_retval; +} oe_syscall_getegid_ocall_args_t; + +typedef struct _oe_syscall_getpgid_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + int pid; + int ocall_errno; +} oe_syscall_getpgid_ocall_args_t; + +typedef struct _oe_syscall_getgroups_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + size_t size; + unsigned int* list; + int ocall_errno; +} oe_syscall_getgroups_ocall_args_t; + +typedef struct _oe_syscall_uname_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + int oe_retval; + struct oe_utsname* buf; + int ocall_errno; +} oe_syscall_uname_ocall_args_t; + +typedef struct _oe_get_supported_attester_format_ids_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + format_ids_t* format_ids; +} oe_get_supported_attester_format_ids_ocall_args_t; + +typedef struct _oe_get_qetarget_info_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + oe_uuid_t* format_id; + void* opt_params; + size_t opt_params_size; + sgx_target_info_t* target_info; +} oe_get_qetarget_info_ocall_args_t; + +typedef struct _oe_get_quote_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + oe_uuid_t* format_id; + void* opt_params; + size_t opt_params_size; + sgx_report_t* sgx_report; + void* quote; + size_t quote_size; + size_t* quote_size_out; +} oe_get_quote_ocall_args_t; + +typedef struct _oe_get_quote_verification_collateral_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + uint8_t* fmspc; + uint8_t collateral_provider; + void* tcb_info; + size_t tcb_info_size; + size_t* tcb_info_size_out; + void* tcb_info_issuer_chain; + size_t tcb_info_issuer_chain_size; + size_t* tcb_info_issuer_chain_size_out; + void* pck_crl; + size_t pck_crl_size; + size_t* pck_crl_size_out; + void* root_ca_crl; + size_t root_ca_crl_size; + size_t* root_ca_crl_size_out; + void* pck_crl_issuer_chain; + size_t pck_crl_issuer_chain_size; + size_t* pck_crl_issuer_chain_size_out; + void* qe_identity; + size_t qe_identity_size; + size_t* qe_identity_size_out; + void* qe_identity_issuer_chain; + size_t qe_identity_issuer_chain_size; + size_t* qe_identity_issuer_chain_size_out; +} oe_get_quote_verification_collateral_ocall_args_t; + +typedef struct _oe_verify_quote_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + oe_uuid_t* format_id; + void* opt_params; + size_t opt_params_size; + void* p_quote; + uint32_t quote_size; + time_t expiration_check_date; + uint32_t* p_collateral_expiration_status; + uint32_t* p_quote_verification_result; + void* p_qve_report_info; + uint32_t qve_report_info_size; + void* p_supplemental_data; + uint32_t supplemental_data_size; + uint32_t* p_supplemental_data_size_out; + uint32_t collateral_version; + void* p_tcb_info; + uint32_t tcb_info_size; + void* p_tcb_info_issuer_chain; + uint32_t tcb_info_issuer_chain_size; + void* p_pck_crl; + uint32_t pck_crl_size; + void* p_root_ca_crl; + uint32_t root_ca_crl_size; + void* p_pck_crl_issuer_chain; + uint32_t pck_crl_issuer_chain_size; + void* p_qe_identity; + uint32_t qe_identity_size; + void* p_qe_identity_issuer_chain; + uint32_t qe_identity_issuer_chain_size; +} oe_verify_quote_ocall_args_t; + +typedef struct _oe_sgx_get_cpuid_table_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + void* cpuid_table_buffer; + size_t cpuid_table_buffer_size; +} oe_sgx_get_cpuid_table_ocall_args_t; + +typedef struct _oe_sgx_backtrace_symbols_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_result_t oe_retval; + oe_enclave_t* oe_enclave; + uint64_t* buffer; + size_t size; + void* symbols_buffer; + size_t symbols_buffer_size; + size_t* symbols_buffer_size_out; +} oe_sgx_backtrace_symbols_ocall_args_t; + +typedef struct _oe_sgx_thread_wake_wait_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_enclave_t* oe_enclave; + uint64_t waiter_tcs; + uint64_t self_tcs; +} oe_sgx_thread_wake_wait_ocall_args_t; + +typedef struct _oe_sgx_wake_switchless_worker_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_host_worker_context_t* context; +} oe_sgx_wake_switchless_worker_ocall_args_t; + +typedef struct _oe_sgx_sleep_switchless_worker_ocall_args_t +{ + oe_result_t oe_result; + uint8_t* deepcopy_out_buffer; + size_t deepcopy_out_buffer_size; + oe_enclave_worker_context_t* context; +} oe_sgx_sleep_switchless_worker_ocall_args_t; + +/**** OCALL functions. ****/ + +static void ocall_oe_syscall_epoll_create1_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_epoll_create1_ocall_args_t* _pargs_in = (oe_syscall_epoll_create1_ocall_args_t*)input_buffer; + oe_syscall_epoll_create1_ocall_args_t* _pargs_out = (oe_syscall_epoll_create1_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_epoll_create1_ocall( + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_epoll_wait_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_epoll_wait_ocall_args_t* _pargs_in = (oe_syscall_epoll_wait_ocall_args_t*)input_buffer; + oe_syscall_epoll_wait_ocall_args_t* _pargs_out = (oe_syscall_epoll_wait_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->events) + OE_SET_OUT_POINTER(events, _pargs_in->maxevents, sizeof(struct oe_epoll_event), struct oe_epoll_event*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_epoll_wait_ocall( + _pargs_in->epfd, + _pargs_in->events, + _pargs_in->maxevents, + _pargs_in->timeout); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_epoll_wake_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_epoll_wake_ocall_args_t* _pargs_in = (oe_syscall_epoll_wake_ocall_args_t*)input_buffer; + oe_syscall_epoll_wake_ocall_args_t* _pargs_out = (oe_syscall_epoll_wake_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_epoll_wake_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_epoll_ctl_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_epoll_ctl_ocall_args_t* _pargs_in = (oe_syscall_epoll_ctl_ocall_args_t*)input_buffer; + oe_syscall_epoll_ctl_ocall_args_t* _pargs_out = (oe_syscall_epoll_ctl_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->event) + OE_SET_IN_POINTER(event, 1, sizeof(struct oe_epoll_event), struct oe_epoll_event*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_epoll_ctl_ocall( + _pargs_in->epfd, + _pargs_in->op, + _pargs_in->fd, + _pargs_in->event); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_epoll_close_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_epoll_close_ocall_args_t* _pargs_in = (oe_syscall_epoll_close_ocall_args_t*)input_buffer; + oe_syscall_epoll_close_ocall_args_t* _pargs_out = (oe_syscall_epoll_close_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_epoll_close_ocall( + _pargs_in->epfd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_open_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_open_ocall_args_t* _pargs_in = (oe_syscall_open_ocall_args_t*)input_buffer; + oe_syscall_open_ocall_args_t* _pargs_out = (oe_syscall_open_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_open_ocall( + (const char*)_pargs_in->pathname, + _pargs_in->flags, + _pargs_in->mode); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_read_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_read_ocall_args_t* _pargs_in = (oe_syscall_read_ocall_args_t*)input_buffer; + oe_syscall_read_ocall_args_t* _pargs_out = (oe_syscall_read_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, _pargs_in->count, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_read_ocall( + _pargs_in->fd, + _pargs_in->buf, + _pargs_in->count); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_write_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_write_ocall_args_t* _pargs_in = (oe_syscall_write_ocall_args_t*)input_buffer; + oe_syscall_write_ocall_args_t* _pargs_out = (oe_syscall_write_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->buf) + OE_SET_IN_POINTER(buf, 1, _pargs_in->count, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_write_ocall( + _pargs_in->fd, + (const void*)_pargs_in->buf, + _pargs_in->count); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_readv_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_readv_ocall_args_t* _pargs_in = (oe_syscall_readv_ocall_args_t*)input_buffer; + oe_syscall_readv_ocall_args_t* _pargs_out = (oe_syscall_readv_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->iov_buf) + OE_SET_IN_OUT_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->iov_buf) + OE_COPY_AND_SET_IN_OUT_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_readv_ocall( + _pargs_in->fd, + _pargs_in->iov_buf, + _pargs_in->iovcnt, + _pargs_in->iov_buf_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_writev_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_writev_ocall_args_t* _pargs_in = (oe_syscall_writev_ocall_args_t*)input_buffer; + oe_syscall_writev_ocall_args_t* _pargs_out = (oe_syscall_writev_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->iov_buf) + OE_SET_IN_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_writev_ocall( + _pargs_in->fd, + (const void*)_pargs_in->iov_buf, + _pargs_in->iovcnt, + _pargs_in->iov_buf_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_lseek_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_lseek_ocall_args_t* _pargs_in = (oe_syscall_lseek_ocall_args_t*)input_buffer; + oe_syscall_lseek_ocall_args_t* _pargs_out = (oe_syscall_lseek_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_lseek_ocall( + _pargs_in->fd, + _pargs_in->offset, + _pargs_in->whence); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_pread_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_pread_ocall_args_t* _pargs_in = (oe_syscall_pread_ocall_args_t*)input_buffer; + oe_syscall_pread_ocall_args_t* _pargs_out = (oe_syscall_pread_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, _pargs_in->count, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_pread_ocall( + _pargs_in->fd, + _pargs_in->buf, + _pargs_in->count, + _pargs_in->offset); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_pwrite_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_pwrite_ocall_args_t* _pargs_in = (oe_syscall_pwrite_ocall_args_t*)input_buffer; + oe_syscall_pwrite_ocall_args_t* _pargs_out = (oe_syscall_pwrite_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->buf) + OE_SET_IN_POINTER(buf, 1, _pargs_in->count, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_pwrite_ocall( + _pargs_in->fd, + (const void*)_pargs_in->buf, + _pargs_in->count, + _pargs_in->offset); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_close_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_close_ocall_args_t* _pargs_in = (oe_syscall_close_ocall_args_t*)input_buffer; + oe_syscall_close_ocall_args_t* _pargs_out = (oe_syscall_close_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_close_ocall( + _pargs_in->fd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_flock_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_flock_ocall_args_t* _pargs_in = (oe_syscall_flock_ocall_args_t*)input_buffer; + oe_syscall_flock_ocall_args_t* _pargs_out = (oe_syscall_flock_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_flock_ocall( + _pargs_in->fd, + _pargs_in->operation); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_fsync_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_fsync_ocall_args_t* _pargs_in = (oe_syscall_fsync_ocall_args_t*)input_buffer; + oe_syscall_fsync_ocall_args_t* _pargs_out = (oe_syscall_fsync_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_fsync_ocall( + _pargs_in->fd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_fdatasync_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_fdatasync_ocall_args_t* _pargs_in = (oe_syscall_fdatasync_ocall_args_t*)input_buffer; + oe_syscall_fdatasync_ocall_args_t* _pargs_out = (oe_syscall_fdatasync_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_fdatasync_ocall( + _pargs_in->fd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_dup_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_dup_ocall_args_t* _pargs_in = (oe_syscall_dup_ocall_args_t*)input_buffer; + oe_syscall_dup_ocall_args_t* _pargs_out = (oe_syscall_dup_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_dup_ocall( + _pargs_in->oldfd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_opendir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_opendir_ocall_args_t* _pargs_in = (oe_syscall_opendir_ocall_args_t*)input_buffer; + oe_syscall_opendir_ocall_args_t* _pargs_out = (oe_syscall_opendir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->name) + OE_SET_IN_POINTER(name, _pargs_in->name_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_opendir_ocall( + (const char*)_pargs_in->name); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_readdir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_readdir_ocall_args_t* _pargs_in = (oe_syscall_readdir_ocall_args_t*)input_buffer; + oe_syscall_readdir_ocall_args_t* _pargs_out = (oe_syscall_readdir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->entry) + OE_SET_OUT_POINTER(entry, 1, sizeof(struct oe_dirent), struct oe_dirent*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_readdir_ocall( + _pargs_in->dirp, + _pargs_in->entry); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_rewinddir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_rewinddir_ocall_args_t* _pargs_in = (oe_syscall_rewinddir_ocall_args_t*)input_buffer; + oe_syscall_rewinddir_ocall_args_t* _pargs_out = (oe_syscall_rewinddir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + oe_syscall_rewinddir_ocall( + _pargs_in->dirp); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_closedir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_closedir_ocall_args_t* _pargs_in = (oe_syscall_closedir_ocall_args_t*)input_buffer; + oe_syscall_closedir_ocall_args_t* _pargs_out = (oe_syscall_closedir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_closedir_ocall( + _pargs_in->dirp); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_stat_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_stat_ocall_args_t* _pargs_in = (oe_syscall_stat_ocall_args_t*)input_buffer; + oe_syscall_stat_ocall_args_t* _pargs_out = (oe_syscall_stat_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, sizeof(struct oe_stat_t), struct oe_stat_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_stat_ocall( + (const char*)_pargs_in->pathname, + _pargs_in->buf); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_fstat_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_fstat_ocall_args_t* _pargs_in = (oe_syscall_fstat_ocall_args_t*)input_buffer; + oe_syscall_fstat_ocall_args_t* _pargs_out = (oe_syscall_fstat_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, sizeof(struct oe_stat_t), struct oe_stat_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_fstat_ocall( + _pargs_in->fd, + _pargs_in->buf); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_access_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_access_ocall_args_t* _pargs_in = (oe_syscall_access_ocall_args_t*)input_buffer; + oe_syscall_access_ocall_args_t* _pargs_out = (oe_syscall_access_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_access_ocall( + (const char*)_pargs_in->pathname, + _pargs_in->mode); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_link_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_link_ocall_args_t* _pargs_in = (oe_syscall_link_ocall_args_t*)input_buffer; + oe_syscall_link_ocall_args_t* _pargs_out = (oe_syscall_link_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->oldpath) + OE_SET_IN_POINTER(oldpath, _pargs_in->oldpath_len, sizeof(char), char*); + if (_pargs_in->newpath) + OE_SET_IN_POINTER(newpath, _pargs_in->newpath_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_link_ocall( + (const char*)_pargs_in->oldpath, + (const char*)_pargs_in->newpath); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_unlink_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_unlink_ocall_args_t* _pargs_in = (oe_syscall_unlink_ocall_args_t*)input_buffer; + oe_syscall_unlink_ocall_args_t* _pargs_out = (oe_syscall_unlink_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_unlink_ocall( + (const char*)_pargs_in->pathname); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_rename_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_rename_ocall_args_t* _pargs_in = (oe_syscall_rename_ocall_args_t*)input_buffer; + oe_syscall_rename_ocall_args_t* _pargs_out = (oe_syscall_rename_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->oldpath) + OE_SET_IN_POINTER(oldpath, _pargs_in->oldpath_len, sizeof(char), char*); + if (_pargs_in->newpath) + OE_SET_IN_POINTER(newpath, _pargs_in->newpath_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_rename_ocall( + (const char*)_pargs_in->oldpath, + (const char*)_pargs_in->newpath); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_truncate_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_truncate_ocall_args_t* _pargs_in = (oe_syscall_truncate_ocall_args_t*)input_buffer; + oe_syscall_truncate_ocall_args_t* _pargs_out = (oe_syscall_truncate_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->path) + OE_SET_IN_POINTER(path, _pargs_in->path_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_truncate_ocall( + (const char*)_pargs_in->path, + _pargs_in->length); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_ftruncate_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_ftruncate_ocall_args_t* _pargs_in = (oe_syscall_ftruncate_ocall_args_t*)input_buffer; + oe_syscall_ftruncate_ocall_args_t* _pargs_out = (oe_syscall_ftruncate_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_ftruncate_ocall( + _pargs_in->fd, + _pargs_in->length); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_mkdir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_mkdir_ocall_args_t* _pargs_in = (oe_syscall_mkdir_ocall_args_t*)input_buffer; + oe_syscall_mkdir_ocall_args_t* _pargs_out = (oe_syscall_mkdir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_mkdir_ocall( + (const char*)_pargs_in->pathname, + _pargs_in->mode); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_rmdir_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_rmdir_ocall_args_t* _pargs_in = (oe_syscall_rmdir_ocall_args_t*)input_buffer; + oe_syscall_rmdir_ocall_args_t* _pargs_out = (oe_syscall_rmdir_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->pathname) + OE_SET_IN_POINTER(pathname, _pargs_in->pathname_len, sizeof(char), char*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_rmdir_ocall( + (const char*)_pargs_in->pathname); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_fcntl_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_fcntl_ocall_args_t* _pargs_in = (oe_syscall_fcntl_ocall_args_t*)input_buffer; + oe_syscall_fcntl_ocall_args_t* _pargs_out = (oe_syscall_fcntl_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->argout) + OE_SET_IN_OUT_POINTER(argout, 1, _pargs_in->argsize, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->argout) + OE_COPY_AND_SET_IN_OUT_POINTER(argout, 1, _pargs_in->argsize, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_fcntl_ocall( + _pargs_in->fd, + _pargs_in->cmd, + _pargs_in->arg, + _pargs_in->argsize, + _pargs_in->argout); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_ioctl_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_ioctl_ocall_args_t* _pargs_in = (oe_syscall_ioctl_ocall_args_t*)input_buffer; + oe_syscall_ioctl_ocall_args_t* _pargs_out = (oe_syscall_ioctl_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->argout) + OE_SET_IN_OUT_POINTER(argout, 1, _pargs_in->argsize, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->argout) + OE_COPY_AND_SET_IN_OUT_POINTER(argout, 1, _pargs_in->argsize, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_ioctl_ocall( + _pargs_in->fd, + _pargs_in->request, + _pargs_in->arg, + _pargs_in->argsize, + _pargs_in->argout); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_poll_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_poll_ocall_args_t* _pargs_in = (oe_syscall_poll_ocall_args_t*)input_buffer; + oe_syscall_poll_ocall_args_t* _pargs_out = (oe_syscall_poll_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->host_fds) + OE_SET_IN_OUT_POINTER(host_fds, _pargs_in->nfds, sizeof(struct oe_host_pollfd), struct oe_host_pollfd*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->host_fds) + OE_COPY_AND_SET_IN_OUT_POINTER(host_fds, _pargs_in->nfds, sizeof(struct oe_host_pollfd), struct oe_host_pollfd*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_poll_ocall( + _pargs_in->host_fds, + _pargs_in->nfds, + _pargs_in->timeout); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_kill_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_kill_ocall_args_t* _pargs_in = (oe_syscall_kill_ocall_args_t*)input_buffer; + oe_syscall_kill_ocall_args_t* _pargs_out = (oe_syscall_kill_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_kill_ocall( + _pargs_in->pid, + _pargs_in->signum); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_close_socket_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_close_socket_ocall_args_t* _pargs_in = (oe_syscall_close_socket_ocall_args_t*)input_buffer; + oe_syscall_close_socket_ocall_args_t* _pargs_out = (oe_syscall_close_socket_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_close_socket_ocall( + _pargs_in->sockfd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_socket_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_socket_ocall_args_t* _pargs_in = (oe_syscall_socket_ocall_args_t*)input_buffer; + oe_syscall_socket_ocall_args_t* _pargs_out = (oe_syscall_socket_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_socket_ocall( + _pargs_in->domain, + _pargs_in->type, + _pargs_in->protocol); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_shutdown_sockets_device_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_shutdown_sockets_device_ocall_args_t* _pargs_in = (oe_syscall_shutdown_sockets_device_ocall_args_t*)input_buffer; + oe_syscall_shutdown_sockets_device_ocall_args_t* _pargs_out = (oe_syscall_shutdown_sockets_device_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_shutdown_sockets_device_ocall( + _pargs_in->sockfd); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_socketpair_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_socketpair_ocall_args_t* _pargs_in = (oe_syscall_socketpair_ocall_args_t*)input_buffer; + oe_syscall_socketpair_ocall_args_t* _pargs_out = (oe_syscall_socketpair_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->sv) + OE_SET_OUT_POINTER(sv, 1, sizeof(oe_host_fd_t[2]), oe_host_fd_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_socketpair_ocall( + _pargs_in->domain, + _pargs_in->type, + _pargs_in->protocol, + *(oe_host_fd_t(*)[2])_pargs_in->sv); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_connect_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_connect_ocall_args_t* _pargs_in = (oe_syscall_connect_ocall_args_t*)input_buffer; + oe_syscall_connect_ocall_args_t* _pargs_out = (oe_syscall_connect_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->addr) + OE_SET_IN_POINTER(addr, 1, _pargs_in->addrlen, struct oe_sockaddr*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_connect_ocall( + _pargs_in->sockfd, + (const struct oe_sockaddr*)_pargs_in->addr, + _pargs_in->addrlen); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_accept_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_accept_ocall_args_t* _pargs_in = (oe_syscall_accept_ocall_args_t*)input_buffer; + oe_syscall_accept_ocall_args_t* _pargs_out = (oe_syscall_accept_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->addr) + OE_SET_OUT_POINTER(addr, 1, _pargs_in->addrlen_in, struct oe_sockaddr*); + if (_pargs_in->addrlen_out) + OE_SET_OUT_POINTER(addrlen_out, 1, sizeof(oe_socklen_t), oe_socklen_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_accept_ocall( + _pargs_in->sockfd, + _pargs_in->addr, + _pargs_in->addrlen_in, + _pargs_in->addrlen_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_bind_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_bind_ocall_args_t* _pargs_in = (oe_syscall_bind_ocall_args_t*)input_buffer; + oe_syscall_bind_ocall_args_t* _pargs_out = (oe_syscall_bind_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->addr) + OE_SET_IN_POINTER(addr, 1, _pargs_in->addrlen, struct oe_sockaddr*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_bind_ocall( + _pargs_in->sockfd, + (const struct oe_sockaddr*)_pargs_in->addr, + _pargs_in->addrlen); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_listen_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_listen_ocall_args_t* _pargs_in = (oe_syscall_listen_ocall_args_t*)input_buffer; + oe_syscall_listen_ocall_args_t* _pargs_out = (oe_syscall_listen_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_listen_ocall( + _pargs_in->sockfd, + _pargs_in->backlog); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_recvmsg_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_recvmsg_ocall_args_t* _pargs_in = (oe_syscall_recvmsg_ocall_args_t*)input_buffer; + oe_syscall_recvmsg_ocall_args_t* _pargs_out = (oe_syscall_recvmsg_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->msg_iov_buf) + OE_SET_IN_OUT_POINTER(msg_iov_buf, 1, _pargs_in->msg_iov_buf_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->msg_name) + OE_SET_OUT_POINTER(msg_name, 1, _pargs_in->msg_namelen, void*); + if (_pargs_in->msg_namelen_out) + OE_SET_OUT_POINTER(msg_namelen_out, 1, sizeof(oe_socklen_t), oe_socklen_t*); + if (_pargs_in->msg_iov_buf) + OE_COPY_AND_SET_IN_OUT_POINTER(msg_iov_buf, 1, _pargs_in->msg_iov_buf_size, void*); + if (_pargs_in->msg_control) + OE_SET_OUT_POINTER(msg_control, 1, _pargs_in->msg_controllen, void*); + if (_pargs_in->msg_controllen_out) + OE_SET_OUT_POINTER(msg_controllen_out, 1, sizeof(size_t), size_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_recvmsg_ocall( + _pargs_in->sockfd, + _pargs_in->msg_name, + _pargs_in->msg_namelen, + _pargs_in->msg_namelen_out, + _pargs_in->msg_iov_buf, + _pargs_in->msg_iovlen, + _pargs_in->msg_iov_buf_size, + _pargs_in->msg_control, + _pargs_in->msg_controllen, + _pargs_in->msg_controllen_out, + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_sendmsg_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_sendmsg_ocall_args_t* _pargs_in = (oe_syscall_sendmsg_ocall_args_t*)input_buffer; + oe_syscall_sendmsg_ocall_args_t* _pargs_out = (oe_syscall_sendmsg_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->msg_name) + OE_SET_IN_POINTER(msg_name, 1, _pargs_in->msg_namelen, void*); + if (_pargs_in->msg_iov_buf) + OE_SET_IN_POINTER(msg_iov_buf, 1, _pargs_in->msg_iov_buf_size, void*); + if (_pargs_in->msg_control) + OE_SET_IN_POINTER(msg_control, 1, _pargs_in->msg_controllen, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_sendmsg_ocall( + _pargs_in->sockfd, + (const void*)_pargs_in->msg_name, + _pargs_in->msg_namelen, + _pargs_in->msg_iov_buf, + _pargs_in->msg_iovlen, + _pargs_in->msg_iov_buf_size, + (const void*)_pargs_in->msg_control, + _pargs_in->msg_controllen, + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_recv_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_recv_ocall_args_t* _pargs_in = (oe_syscall_recv_ocall_args_t*)input_buffer; + oe_syscall_recv_ocall_args_t* _pargs_out = (oe_syscall_recv_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, _pargs_in->len, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_recv_ocall( + _pargs_in->sockfd, + _pargs_in->buf, + _pargs_in->len, + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_recvfrom_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_recvfrom_ocall_args_t* _pargs_in = (oe_syscall_recvfrom_ocall_args_t*)input_buffer; + oe_syscall_recvfrom_ocall_args_t* _pargs_out = (oe_syscall_recvfrom_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, _pargs_in->len, void*); + if (_pargs_in->src_addr) + OE_SET_OUT_POINTER(src_addr, 1, _pargs_in->addrlen_in, struct oe_sockaddr*); + if (_pargs_in->addrlen_out) + OE_SET_OUT_POINTER(addrlen_out, 1, sizeof(oe_socklen_t), oe_socklen_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_recvfrom_ocall( + _pargs_in->sockfd, + _pargs_in->buf, + _pargs_in->len, + _pargs_in->flags, + _pargs_in->src_addr, + _pargs_in->addrlen_in, + _pargs_in->addrlen_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_send_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_send_ocall_args_t* _pargs_in = (oe_syscall_send_ocall_args_t*)input_buffer; + oe_syscall_send_ocall_args_t* _pargs_out = (oe_syscall_send_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->buf) + OE_SET_IN_POINTER(buf, 1, _pargs_in->len, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_send_ocall( + _pargs_in->sockfd, + (const void*)_pargs_in->buf, + _pargs_in->len, + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_sendto_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_sendto_ocall_args_t* _pargs_in = (oe_syscall_sendto_ocall_args_t*)input_buffer; + oe_syscall_sendto_ocall_args_t* _pargs_out = (oe_syscall_sendto_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->buf) + OE_SET_IN_POINTER(buf, 1, _pargs_in->len, void*); + if (_pargs_in->dest_addr) + OE_SET_IN_POINTER(dest_addr, 1, _pargs_in->addrlen, struct oe_sockaddr*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_sendto_ocall( + _pargs_in->sockfd, + (const void*)_pargs_in->buf, + _pargs_in->len, + _pargs_in->flags, + (const struct oe_sockaddr*)_pargs_in->dest_addr, + _pargs_in->addrlen); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_recvv_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_recvv_ocall_args_t* _pargs_in = (oe_syscall_recvv_ocall_args_t*)input_buffer; + oe_syscall_recvv_ocall_args_t* _pargs_out = (oe_syscall_recvv_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->iov_buf) + OE_SET_IN_OUT_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->iov_buf) + OE_COPY_AND_SET_IN_OUT_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_recvv_ocall( + _pargs_in->fd, + _pargs_in->iov_buf, + _pargs_in->iovcnt, + _pargs_in->iov_buf_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_sendv_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_sendv_ocall_args_t* _pargs_in = (oe_syscall_sendv_ocall_args_t*)input_buffer; + oe_syscall_sendv_ocall_args_t* _pargs_out = (oe_syscall_sendv_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->iov_buf) + OE_SET_IN_POINTER(iov_buf, 1, _pargs_in->iov_buf_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_sendv_ocall( + _pargs_in->fd, + (const void*)_pargs_in->iov_buf, + _pargs_in->iovcnt, + _pargs_in->iov_buf_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_shutdown_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_shutdown_ocall_args_t* _pargs_in = (oe_syscall_shutdown_ocall_args_t*)input_buffer; + oe_syscall_shutdown_ocall_args_t* _pargs_out = (oe_syscall_shutdown_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_shutdown_ocall( + _pargs_in->sockfd, + _pargs_in->how); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_setsockopt_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_setsockopt_ocall_args_t* _pargs_in = (oe_syscall_setsockopt_ocall_args_t*)input_buffer; + oe_syscall_setsockopt_ocall_args_t* _pargs_out = (oe_syscall_setsockopt_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->optval) + OE_SET_IN_POINTER(optval, 1, _pargs_in->optlen, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_setsockopt_ocall( + _pargs_in->sockfd, + _pargs_in->level, + _pargs_in->optname, + (const void*)_pargs_in->optval, + _pargs_in->optlen); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getsockopt_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getsockopt_ocall_args_t* _pargs_in = (oe_syscall_getsockopt_ocall_args_t*)input_buffer; + oe_syscall_getsockopt_ocall_args_t* _pargs_out = (oe_syscall_getsockopt_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->optval) + OE_SET_OUT_POINTER(optval, 1, _pargs_in->optlen_in, void*); + if (_pargs_in->optlen_out) + OE_SET_OUT_POINTER(optlen_out, 1, sizeof(oe_socklen_t), oe_socklen_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getsockopt_ocall( + _pargs_in->sockfd, + _pargs_in->level, + _pargs_in->optname, + _pargs_in->optval, + _pargs_in->optlen_in, + _pargs_in->optlen_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getsockname_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getsockname_ocall_args_t* _pargs_in = (oe_syscall_getsockname_ocall_args_t*)input_buffer; + oe_syscall_getsockname_ocall_args_t* _pargs_out = (oe_syscall_getsockname_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->addr) + OE_SET_OUT_POINTER(addr, 1, _pargs_in->addrlen_in, struct oe_sockaddr*); + if (_pargs_in->addrlen_out) + OE_SET_OUT_POINTER(addrlen_out, 1, 1, oe_socklen_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getsockname_ocall( + _pargs_in->sockfd, + _pargs_in->addr, + _pargs_in->addrlen_in, + _pargs_in->addrlen_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getpeername_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getpeername_ocall_args_t* _pargs_in = (oe_syscall_getpeername_ocall_args_t*)input_buffer; + oe_syscall_getpeername_ocall_args_t* _pargs_out = (oe_syscall_getpeername_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->addr) + OE_SET_OUT_POINTER(addr, 1, _pargs_in->addrlen_in, struct oe_sockaddr*); + if (_pargs_in->addrlen_out) + OE_SET_OUT_POINTER(addrlen_out, 1, 1, oe_socklen_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getpeername_ocall( + _pargs_in->sockfd, + _pargs_in->addr, + _pargs_in->addrlen_in, + _pargs_in->addrlen_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getaddrinfo_open_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getaddrinfo_open_ocall_args_t* _pargs_in = (oe_syscall_getaddrinfo_open_ocall_args_t*)input_buffer; + oe_syscall_getaddrinfo_open_ocall_args_t* _pargs_out = (oe_syscall_getaddrinfo_open_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->node) + OE_SET_IN_POINTER(node, _pargs_in->node_len, sizeof(char), char*); + if (_pargs_in->service) + OE_SET_IN_POINTER(service, _pargs_in->service_len, sizeof(char), char*); + if (_pargs_in->hints) + OE_SET_IN_POINTER(hints, 1, sizeof(struct oe_addrinfo), struct oe_addrinfo*); + if (_pargs_in->hints && _pargs_in->hints->ai_addr) + OE_SET_IN_POINTER(hints->ai_addr, 1, _pargs_in->hints->ai_addrlen, struct oe_sockaddr*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->handle) + OE_SET_OUT_POINTER(handle, 1, sizeof(uint64_t), uint64_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getaddrinfo_open_ocall( + (const char*)_pargs_in->node, + (const char*)_pargs_in->service, + (const struct oe_addrinfo*)_pargs_in->hints, + _pargs_in->handle); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getaddrinfo_read_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getaddrinfo_read_ocall_args_t* _pargs_in = (oe_syscall_getaddrinfo_read_ocall_args_t*)input_buffer; + oe_syscall_getaddrinfo_read_ocall_args_t* _pargs_out = (oe_syscall_getaddrinfo_read_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->ai_flags) + OE_SET_OUT_POINTER(ai_flags, 1, sizeof(int), int*); + if (_pargs_in->ai_family) + OE_SET_OUT_POINTER(ai_family, 1, sizeof(int), int*); + if (_pargs_in->ai_socktype) + OE_SET_OUT_POINTER(ai_socktype, 1, sizeof(int), int*); + if (_pargs_in->ai_protocol) + OE_SET_OUT_POINTER(ai_protocol, 1, sizeof(int), int*); + if (_pargs_in->ai_addrlen) + OE_SET_OUT_POINTER(ai_addrlen, 1, sizeof(oe_socklen_t), oe_socklen_t*); + if (_pargs_in->ai_addr) + OE_SET_OUT_POINTER(ai_addr, 1, _pargs_in->ai_addrlen_in, struct oe_sockaddr*); + if (_pargs_in->ai_canonnamelen) + OE_SET_OUT_POINTER(ai_canonnamelen, 1, sizeof(size_t), size_t*); + if (_pargs_in->ai_canonname) + OE_SET_OUT_POINTER(ai_canonname, 1, _pargs_in->ai_canonnamelen_in, char*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getaddrinfo_read_ocall( + _pargs_in->handle, + _pargs_in->ai_flags, + _pargs_in->ai_family, + _pargs_in->ai_socktype, + _pargs_in->ai_protocol, + _pargs_in->ai_addrlen_in, + _pargs_in->ai_addrlen, + _pargs_in->ai_addr, + _pargs_in->ai_canonnamelen_in, + _pargs_in->ai_canonnamelen, + _pargs_in->ai_canonname); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getaddrinfo_close_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getaddrinfo_close_ocall_args_t* _pargs_in = (oe_syscall_getaddrinfo_close_ocall_args_t*)input_buffer; + oe_syscall_getaddrinfo_close_ocall_args_t* _pargs_out = (oe_syscall_getaddrinfo_close_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getaddrinfo_close_ocall( + _pargs_in->handle); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getnameinfo_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getnameinfo_ocall_args_t* _pargs_in = (oe_syscall_getnameinfo_ocall_args_t*)input_buffer; + oe_syscall_getnameinfo_ocall_args_t* _pargs_out = (oe_syscall_getnameinfo_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->sa) + OE_SET_IN_POINTER(sa, 1, _pargs_in->salen, struct oe_sockaddr*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->host) + OE_SET_OUT_POINTER(host, 1, _pargs_in->hostlen, char*); + if (_pargs_in->serv) + OE_SET_OUT_POINTER(serv, 1, _pargs_in->servlen, char*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getnameinfo_ocall( + (const struct oe_sockaddr*)_pargs_in->sa, + _pargs_in->salen, + _pargs_in->host, + _pargs_in->hostlen, + _pargs_in->serv, + _pargs_in->servlen, + _pargs_in->flags); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_nanosleep_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_nanosleep_ocall_args_t* _pargs_in = (oe_syscall_nanosleep_ocall_args_t*)input_buffer; + oe_syscall_nanosleep_ocall_args_t* _pargs_out = (oe_syscall_nanosleep_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->req) + OE_SET_IN_POINTER(req, 1, sizeof(struct oe_timespec), struct oe_timespec*); + if (_pargs_in->rem) + OE_SET_IN_OUT_POINTER(rem, 1, sizeof(struct oe_timespec), struct oe_timespec*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->rem) + OE_COPY_AND_SET_IN_OUT_POINTER(rem, 1, sizeof(struct oe_timespec), struct oe_timespec*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_nanosleep_ocall( + _pargs_in->req, + _pargs_in->rem); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_clock_nanosleep_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_clock_nanosleep_ocall_args_t* _pargs_in = (oe_syscall_clock_nanosleep_ocall_args_t*)input_buffer; + oe_syscall_clock_nanosleep_ocall_args_t* _pargs_out = (oe_syscall_clock_nanosleep_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->req) + OE_SET_IN_POINTER(req, 1, sizeof(struct oe_timespec), struct oe_timespec*); + if (_pargs_in->rem) + OE_SET_IN_OUT_POINTER(rem, 1, sizeof(struct oe_timespec), struct oe_timespec*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->rem) + OE_COPY_AND_SET_IN_OUT_POINTER(rem, 1, sizeof(struct oe_timespec), struct oe_timespec*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_clock_nanosleep_ocall( + _pargs_in->clockid, + _pargs_in->flag, + _pargs_in->req, + _pargs_in->rem); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getpid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getpid_ocall_args_t* _pargs_in = (oe_syscall_getpid_ocall_args_t*)input_buffer; + oe_syscall_getpid_ocall_args_t* _pargs_out = (oe_syscall_getpid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getpid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getppid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getppid_ocall_args_t* _pargs_in = (oe_syscall_getppid_ocall_args_t*)input_buffer; + oe_syscall_getppid_ocall_args_t* _pargs_out = (oe_syscall_getppid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getppid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getpgrp_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getpgrp_ocall_args_t* _pargs_in = (oe_syscall_getpgrp_ocall_args_t*)input_buffer; + oe_syscall_getpgrp_ocall_args_t* _pargs_out = (oe_syscall_getpgrp_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getpgrp_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getuid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getuid_ocall_args_t* _pargs_in = (oe_syscall_getuid_ocall_args_t*)input_buffer; + oe_syscall_getuid_ocall_args_t* _pargs_out = (oe_syscall_getuid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getuid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_geteuid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_geteuid_ocall_args_t* _pargs_in = (oe_syscall_geteuid_ocall_args_t*)input_buffer; + oe_syscall_geteuid_ocall_args_t* _pargs_out = (oe_syscall_geteuid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_geteuid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getgid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getgid_ocall_args_t* _pargs_in = (oe_syscall_getgid_ocall_args_t*)input_buffer; + oe_syscall_getgid_ocall_args_t* _pargs_out = (oe_syscall_getgid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getgid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getegid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getegid_ocall_args_t* _pargs_in = (oe_syscall_getegid_ocall_args_t*)input_buffer; + oe_syscall_getegid_ocall_args_t* _pargs_out = (oe_syscall_getegid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getegid_ocall( + ); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getpgid_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getpgid_ocall_args_t* _pargs_in = (oe_syscall_getpgid_ocall_args_t*)input_buffer; + oe_syscall_getpgid_ocall_args_t* _pargs_out = (oe_syscall_getpgid_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getpgid_ocall( + _pargs_in->pid); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_getgroups_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_getgroups_ocall_args_t* _pargs_in = (oe_syscall_getgroups_ocall_args_t*)input_buffer; + oe_syscall_getgroups_ocall_args_t* _pargs_out = (oe_syscall_getgroups_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->list) + OE_SET_OUT_POINTER(list, _pargs_in->size, sizeof(unsigned int), unsigned int*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_getgroups_ocall( + _pargs_in->size, + _pargs_in->list); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_syscall_uname_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_syscall_uname_ocall_args_t* _pargs_in = (oe_syscall_uname_ocall_args_t*)input_buffer; + oe_syscall_uname_ocall_args_t* _pargs_out = (oe_syscall_uname_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->buf) + OE_SET_OUT_POINTER(buf, 1, sizeof(struct oe_utsname), struct oe_utsname*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_syscall_uname_ocall( + _pargs_in->buf); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + _pargs_out->ocall_errno = errno; + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_get_supported_attester_format_ids_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_get_supported_attester_format_ids_ocall_args_t* _pargs_in = (oe_get_supported_attester_format_ids_ocall_args_t*)input_buffer; + oe_get_supported_attester_format_ids_ocall_args_t* _pargs_out = (oe_get_supported_attester_format_ids_ocall_args_t*)output_buffer; + + uint8_t* _deepcopy_out_buffer = NULL; + size_t _deepcopy_out_buffer_offset = 0; + size_t _deepcopy_out_buffer_size = 0; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->format_ids) + OE_SET_OUT_POINTER(format_ids, 1, sizeof(format_ids_t), format_ids_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_get_supported_attester_format_ids_ocall( + _pargs_in->format_ids); + + /* Compute the size for the deep-copy out buffer. */ + if (_pargs_in->format_ids && _pargs_in->format_ids->data) + OE_ADD_ARG_SIZE(_deepcopy_out_buffer_size, 1, _pargs_in->format_ids->size); + + if (_deepcopy_out_buffer_size) + { + _deepcopy_out_buffer = (uint8_t*) oe_malloc(_deepcopy_out_buffer_size); + if (!_deepcopy_out_buffer) + { + _result = OE_OUT_OF_MEMORY; + goto done; + } + } + + /* Serialize the deep-copied content into the buffer. */ + if (_pargs_in->format_ids && _pargs_in->format_ids->data) + OE_WRITE_DEEPCOPY_OUT_PARAM(_pargs_in->format_ids->data, 1, _pargs_in->format_ids->size); + + if (_deepcopy_out_buffer_offset != _deepcopy_out_buffer_size) + { + _result = OE_FAILURE; + goto done; + } + + /* Set the _deepcopy_out_buffer and _deepcopy_out_buffer as part of _pargs_out. */ + _pargs_out->deepcopy_out_buffer = _deepcopy_out_buffer; + _pargs_out->deepcopy_out_buffer_size = _deepcopy_out_buffer_size; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + /* Free _pargs_out->deepcopy_out_buffer on failure. */ + if (_result != OE_OK) + { + oe_free(_pargs_out->deepcopy_out_buffer); + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + } + + /* Free nested buffers allocated by the user function. */ + if (_pargs_in->format_ids) + { + for (size_t _i_1 = 0; _i_1 < 1; _i_1++) + { + free(_pargs_in->format_ids[_i_1].data); + } + } + + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_get_qetarget_info_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_get_qetarget_info_ocall_args_t* _pargs_in = (oe_get_qetarget_info_ocall_args_t*)input_buffer; + oe_get_qetarget_info_ocall_args_t* _pargs_out = (oe_get_qetarget_info_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->format_id) + OE_SET_IN_POINTER(format_id, 1, sizeof(oe_uuid_t), oe_uuid_t*); + if (_pargs_in->opt_params) + OE_SET_IN_POINTER(opt_params, 1, _pargs_in->opt_params_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->target_info) + OE_SET_OUT_POINTER(target_info, 1, sizeof(sgx_target_info_t), sgx_target_info_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_get_qetarget_info_ocall( + (const oe_uuid_t*)_pargs_in->format_id, + (const void*)_pargs_in->opt_params, + _pargs_in->opt_params_size, + _pargs_in->target_info); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_get_quote_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_get_quote_ocall_args_t* _pargs_in = (oe_get_quote_ocall_args_t*)input_buffer; + oe_get_quote_ocall_args_t* _pargs_out = (oe_get_quote_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->format_id) + OE_SET_IN_POINTER(format_id, 1, sizeof(oe_uuid_t), oe_uuid_t*); + if (_pargs_in->opt_params) + OE_SET_IN_POINTER(opt_params, 1, _pargs_in->opt_params_size, void*); + if (_pargs_in->sgx_report) + OE_SET_IN_POINTER(sgx_report, 1, sizeof(sgx_report_t), sgx_report_t*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->quote) + OE_SET_OUT_POINTER(quote, 1, _pargs_in->quote_size, void*); + if (_pargs_in->quote_size_out) + OE_SET_OUT_POINTER(quote_size_out, 1, sizeof(size_t), size_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_get_quote_ocall( + (const oe_uuid_t*)_pargs_in->format_id, + (const void*)_pargs_in->opt_params, + _pargs_in->opt_params_size, + (const sgx_report_t*)_pargs_in->sgx_report, + _pargs_in->quote, + _pargs_in->quote_size, + _pargs_in->quote_size_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_get_quote_verification_collateral_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_get_quote_verification_collateral_ocall_args_t* _pargs_in = (oe_get_quote_verification_collateral_ocall_args_t*)input_buffer; + oe_get_quote_verification_collateral_ocall_args_t* _pargs_out = (oe_get_quote_verification_collateral_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->fmspc) + OE_SET_IN_POINTER(fmspc, 1, sizeof(uint8_t[6]), uint8_t*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->tcb_info) + OE_SET_OUT_POINTER(tcb_info, 1, _pargs_in->tcb_info_size, void*); + if (_pargs_in->tcb_info_size_out) + OE_SET_OUT_POINTER(tcb_info_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->tcb_info_issuer_chain) + OE_SET_OUT_POINTER(tcb_info_issuer_chain, 1, _pargs_in->tcb_info_issuer_chain_size, void*); + if (_pargs_in->tcb_info_issuer_chain_size_out) + OE_SET_OUT_POINTER(tcb_info_issuer_chain_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->pck_crl) + OE_SET_OUT_POINTER(pck_crl, 1, _pargs_in->pck_crl_size, void*); + if (_pargs_in->pck_crl_size_out) + OE_SET_OUT_POINTER(pck_crl_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->root_ca_crl) + OE_SET_OUT_POINTER(root_ca_crl, 1, _pargs_in->root_ca_crl_size, void*); + if (_pargs_in->root_ca_crl_size_out) + OE_SET_OUT_POINTER(root_ca_crl_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->pck_crl_issuer_chain) + OE_SET_OUT_POINTER(pck_crl_issuer_chain, 1, _pargs_in->pck_crl_issuer_chain_size, void*); + if (_pargs_in->pck_crl_issuer_chain_size_out) + OE_SET_OUT_POINTER(pck_crl_issuer_chain_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->qe_identity) + OE_SET_OUT_POINTER(qe_identity, 1, _pargs_in->qe_identity_size, void*); + if (_pargs_in->qe_identity_size_out) + OE_SET_OUT_POINTER(qe_identity_size_out, 1, sizeof(size_t), size_t*); + if (_pargs_in->qe_identity_issuer_chain) + OE_SET_OUT_POINTER(qe_identity_issuer_chain, 1, _pargs_in->qe_identity_issuer_chain_size, void*); + if (_pargs_in->qe_identity_issuer_chain_size_out) + OE_SET_OUT_POINTER(qe_identity_issuer_chain_size_out, 1, sizeof(size_t), size_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_get_quote_verification_collateral_ocall( + *(uint8_t(*)[6])_pargs_in->fmspc, + _pargs_in->collateral_provider, + _pargs_in->tcb_info, + _pargs_in->tcb_info_size, + _pargs_in->tcb_info_size_out, + _pargs_in->tcb_info_issuer_chain, + _pargs_in->tcb_info_issuer_chain_size, + _pargs_in->tcb_info_issuer_chain_size_out, + _pargs_in->pck_crl, + _pargs_in->pck_crl_size, + _pargs_in->pck_crl_size_out, + _pargs_in->root_ca_crl, + _pargs_in->root_ca_crl_size, + _pargs_in->root_ca_crl_size_out, + _pargs_in->pck_crl_issuer_chain, + _pargs_in->pck_crl_issuer_chain_size, + _pargs_in->pck_crl_issuer_chain_size_out, + _pargs_in->qe_identity, + _pargs_in->qe_identity_size, + _pargs_in->qe_identity_size_out, + _pargs_in->qe_identity_issuer_chain, + _pargs_in->qe_identity_issuer_chain_size, + _pargs_in->qe_identity_issuer_chain_size_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_verify_quote_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_verify_quote_ocall_args_t* _pargs_in = (oe_verify_quote_ocall_args_t*)input_buffer; + oe_verify_quote_ocall_args_t* _pargs_out = (oe_verify_quote_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->format_id) + OE_SET_IN_POINTER(format_id, 1, sizeof(oe_uuid_t), oe_uuid_t*); + if (_pargs_in->opt_params) + OE_SET_IN_POINTER(opt_params, 1, _pargs_in->opt_params_size, void*); + if (_pargs_in->p_quote) + OE_SET_IN_POINTER(p_quote, 1, _pargs_in->quote_size, void*); + if (_pargs_in->p_qve_report_info) + OE_SET_IN_OUT_POINTER(p_qve_report_info, 1, _pargs_in->qve_report_info_size, void*); + if (_pargs_in->p_tcb_info) + OE_SET_IN_POINTER(p_tcb_info, 1, _pargs_in->tcb_info_size, void*); + if (_pargs_in->p_tcb_info_issuer_chain) + OE_SET_IN_POINTER(p_tcb_info_issuer_chain, 1, _pargs_in->tcb_info_issuer_chain_size, void*); + if (_pargs_in->p_pck_crl) + OE_SET_IN_POINTER(p_pck_crl, 1, _pargs_in->pck_crl_size, void*); + if (_pargs_in->p_root_ca_crl) + OE_SET_IN_POINTER(p_root_ca_crl, 1, _pargs_in->root_ca_crl_size, void*); + if (_pargs_in->p_pck_crl_issuer_chain) + OE_SET_IN_POINTER(p_pck_crl_issuer_chain, 1, _pargs_in->pck_crl_issuer_chain_size, void*); + if (_pargs_in->p_qe_identity) + OE_SET_IN_POINTER(p_qe_identity, 1, _pargs_in->qe_identity_size, void*); + if (_pargs_in->p_qe_identity_issuer_chain) + OE_SET_IN_POINTER(p_qe_identity_issuer_chain, 1, _pargs_in->qe_identity_issuer_chain_size, void*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->p_collateral_expiration_status) + OE_SET_OUT_POINTER(p_collateral_expiration_status, 1, sizeof(uint32_t), uint32_t*); + if (_pargs_in->p_quote_verification_result) + OE_SET_OUT_POINTER(p_quote_verification_result, 1, sizeof(uint32_t), uint32_t*); + if (_pargs_in->p_qve_report_info) + OE_COPY_AND_SET_IN_OUT_POINTER(p_qve_report_info, 1, _pargs_in->qve_report_info_size, void*); + if (_pargs_in->p_supplemental_data) + OE_SET_OUT_POINTER(p_supplemental_data, 1, _pargs_in->supplemental_data_size, void*); + if (_pargs_in->p_supplemental_data_size_out) + OE_SET_OUT_POINTER(p_supplemental_data_size_out, 1, sizeof(uint32_t), uint32_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_verify_quote_ocall( + (const oe_uuid_t*)_pargs_in->format_id, + (const void*)_pargs_in->opt_params, + _pargs_in->opt_params_size, + (const void*)_pargs_in->p_quote, + _pargs_in->quote_size, + _pargs_in->expiration_check_date, + _pargs_in->p_collateral_expiration_status, + _pargs_in->p_quote_verification_result, + _pargs_in->p_qve_report_info, + _pargs_in->qve_report_info_size, + _pargs_in->p_supplemental_data, + _pargs_in->supplemental_data_size, + _pargs_in->p_supplemental_data_size_out, + _pargs_in->collateral_version, + (const void*)_pargs_in->p_tcb_info, + _pargs_in->tcb_info_size, + (const void*)_pargs_in->p_tcb_info_issuer_chain, + _pargs_in->tcb_info_issuer_chain_size, + (const void*)_pargs_in->p_pck_crl, + _pargs_in->pck_crl_size, + (const void*)_pargs_in->p_root_ca_crl, + _pargs_in->root_ca_crl_size, + (const void*)_pargs_in->p_pck_crl_issuer_chain, + _pargs_in->pck_crl_issuer_chain_size, + (const void*)_pargs_in->p_qe_identity, + _pargs_in->qe_identity_size, + (const void*)_pargs_in->p_qe_identity_issuer_chain, + _pargs_in->qe_identity_issuer_chain_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_sgx_get_cpuid_table_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_sgx_get_cpuid_table_ocall_args_t* _pargs_in = (oe_sgx_get_cpuid_table_ocall_args_t*)input_buffer; + oe_sgx_get_cpuid_table_ocall_args_t* _pargs_out = (oe_sgx_get_cpuid_table_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->cpuid_table_buffer) + OE_SET_OUT_POINTER(cpuid_table_buffer, 1, _pargs_in->cpuid_table_buffer_size, void*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_sgx_get_cpuid_table_ocall( + _pargs_in->cpuid_table_buffer, + _pargs_in->cpuid_table_buffer_size); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_sgx_backtrace_symbols_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_sgx_backtrace_symbols_ocall_args_t* _pargs_in = (oe_sgx_backtrace_symbols_ocall_args_t*)input_buffer; + oe_sgx_backtrace_symbols_ocall_args_t* _pargs_out = (oe_sgx_backtrace_symbols_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + if (_pargs_in->buffer) + OE_SET_IN_POINTER(buffer, _pargs_in->size, sizeof(uint64_t), uint64_t*); + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + if (_pargs_in->symbols_buffer) + OE_SET_OUT_POINTER(symbols_buffer, 1, _pargs_in->symbols_buffer_size, void*); + if (_pargs_in->symbols_buffer_size_out) + OE_SET_OUT_POINTER(symbols_buffer_size_out, 1, sizeof(size_t), size_t*); + + /* Call user function. */ + _pargs_out->oe_retval = oe_sgx_backtrace_symbols_ocall( + _pargs_in->oe_enclave, + (const uint64_t*)_pargs_in->buffer, + _pargs_in->size, + _pargs_in->symbols_buffer, + _pargs_in->symbols_buffer_size, + _pargs_in->symbols_buffer_size_out); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_sgx_thread_wake_wait_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_sgx_thread_wake_wait_ocall_args_t* _pargs_in = (oe_sgx_thread_wake_wait_ocall_args_t*)input_buffer; + oe_sgx_thread_wake_wait_ocall_args_t* _pargs_out = (oe_sgx_thread_wake_wait_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + oe_sgx_thread_wake_wait_ocall( + _pargs_in->oe_enclave, + _pargs_in->waiter_tcs, + _pargs_in->self_tcs); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_sgx_wake_switchless_worker_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_sgx_wake_switchless_worker_ocall_args_t* _pargs_in = (oe_sgx_wake_switchless_worker_ocall_args_t*)input_buffer; + oe_sgx_wake_switchless_worker_ocall_args_t* _pargs_out = (oe_sgx_wake_switchless_worker_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + oe_sgx_wake_switchless_worker_ocall( + _pargs_in->context); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +static void ocall_oe_sgx_sleep_switchless_worker_ocall( + uint8_t* input_buffer, + size_t input_buffer_size, + uint8_t* output_buffer, + size_t output_buffer_size, + size_t* output_bytes_written) +{ + oe_result_t _result = OE_FAILURE; + OE_UNUSED(input_buffer_size); + + /* Prepare parameters. */ + oe_sgx_sleep_switchless_worker_ocall_args_t* _pargs_in = (oe_sgx_sleep_switchless_worker_ocall_args_t*)input_buffer; + oe_sgx_sleep_switchless_worker_ocall_args_t* _pargs_out = (oe_sgx_sleep_switchless_worker_ocall_args_t*)output_buffer; + + size_t _input_buffer_offset = 0; + size_t _output_buffer_offset = 0; + OE_ADD_SIZE(_input_buffer_offset, sizeof(*_pargs_in)); + OE_ADD_SIZE(_output_buffer_offset, sizeof(*_pargs_out)); + + if (input_buffer_size < sizeof(*_pargs_in) || output_buffer_size < sizeof(*_pargs_in)) + goto done; + + /* Make sure input and output buffers are valid. */ + if (!input_buffer || !output_buffer) { + _result = OE_INVALID_PARAMETER; + goto done; + } + + /* Set in and in-out pointers. */ + /* There were no in nor in-out parameters. */ + + /* Set out and in-out pointers. */ + /* In-out parameters are copied to output buffer. */ + /* There were no out nor in-out parameters. */ + + /* Call user function. */ + oe_sgx_sleep_switchless_worker_ocall( + _pargs_in->context); + + /* There is no deep-copyable out parameter. */ + _pargs_out->deepcopy_out_buffer = NULL; + _pargs_out->deepcopy_out_buffer_size = 0; + + /* Propagate errno back to enclave. */ + /* Errno propagation not enabled. */ + + /* Success. */ + _result = OE_OK; + *output_bytes_written = _output_buffer_offset; + +done: + if (_pargs_out && output_buffer_size >= sizeof(*_pargs_out)) + _pargs_out->oe_result = _result; +} + +/**** OCALL function table. ****/ + +static oe_ocall_func_t _sgx_cpp_ocall_function_table[] = { + (oe_ocall_func_t) ocall_oe_syscall_epoll_create1_ocall, + (oe_ocall_func_t) ocall_oe_syscall_epoll_wait_ocall, + (oe_ocall_func_t) ocall_oe_syscall_epoll_wake_ocall, + (oe_ocall_func_t) ocall_oe_syscall_epoll_ctl_ocall, + (oe_ocall_func_t) ocall_oe_syscall_epoll_close_ocall, + (oe_ocall_func_t) ocall_oe_syscall_open_ocall, + (oe_ocall_func_t) ocall_oe_syscall_read_ocall, + (oe_ocall_func_t) ocall_oe_syscall_write_ocall, + (oe_ocall_func_t) ocall_oe_syscall_readv_ocall, + (oe_ocall_func_t) ocall_oe_syscall_writev_ocall, + (oe_ocall_func_t) ocall_oe_syscall_lseek_ocall, + (oe_ocall_func_t) ocall_oe_syscall_pread_ocall, + (oe_ocall_func_t) ocall_oe_syscall_pwrite_ocall, + (oe_ocall_func_t) ocall_oe_syscall_close_ocall, + (oe_ocall_func_t) ocall_oe_syscall_flock_ocall, + (oe_ocall_func_t) ocall_oe_syscall_fsync_ocall, + (oe_ocall_func_t) ocall_oe_syscall_fdatasync_ocall, + (oe_ocall_func_t) ocall_oe_syscall_dup_ocall, + (oe_ocall_func_t) ocall_oe_syscall_opendir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_readdir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_rewinddir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_closedir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_stat_ocall, + (oe_ocall_func_t) ocall_oe_syscall_fstat_ocall, + (oe_ocall_func_t) ocall_oe_syscall_access_ocall, + (oe_ocall_func_t) ocall_oe_syscall_link_ocall, + (oe_ocall_func_t) ocall_oe_syscall_unlink_ocall, + (oe_ocall_func_t) ocall_oe_syscall_rename_ocall, + (oe_ocall_func_t) ocall_oe_syscall_truncate_ocall, + (oe_ocall_func_t) ocall_oe_syscall_ftruncate_ocall, + (oe_ocall_func_t) ocall_oe_syscall_mkdir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_rmdir_ocall, + (oe_ocall_func_t) ocall_oe_syscall_fcntl_ocall, + (oe_ocall_func_t) ocall_oe_syscall_ioctl_ocall, + (oe_ocall_func_t) ocall_oe_syscall_poll_ocall, + (oe_ocall_func_t) ocall_oe_syscall_kill_ocall, + (oe_ocall_func_t) ocall_oe_syscall_close_socket_ocall, + (oe_ocall_func_t) ocall_oe_syscall_socket_ocall, + (oe_ocall_func_t) ocall_oe_syscall_shutdown_sockets_device_ocall, + (oe_ocall_func_t) ocall_oe_syscall_socketpair_ocall, + (oe_ocall_func_t) ocall_oe_syscall_connect_ocall, + (oe_ocall_func_t) ocall_oe_syscall_accept_ocall, + (oe_ocall_func_t) ocall_oe_syscall_bind_ocall, + (oe_ocall_func_t) ocall_oe_syscall_listen_ocall, + (oe_ocall_func_t) ocall_oe_syscall_recvmsg_ocall, + (oe_ocall_func_t) ocall_oe_syscall_sendmsg_ocall, + (oe_ocall_func_t) ocall_oe_syscall_recv_ocall, + (oe_ocall_func_t) ocall_oe_syscall_recvfrom_ocall, + (oe_ocall_func_t) ocall_oe_syscall_send_ocall, + (oe_ocall_func_t) ocall_oe_syscall_sendto_ocall, + (oe_ocall_func_t) ocall_oe_syscall_recvv_ocall, + (oe_ocall_func_t) ocall_oe_syscall_sendv_ocall, + (oe_ocall_func_t) ocall_oe_syscall_shutdown_ocall, + (oe_ocall_func_t) ocall_oe_syscall_setsockopt_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getsockopt_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getsockname_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getpeername_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getaddrinfo_open_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getaddrinfo_read_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getaddrinfo_close_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getnameinfo_ocall, + (oe_ocall_func_t) ocall_oe_syscall_nanosleep_ocall, + (oe_ocall_func_t) ocall_oe_syscall_clock_nanosleep_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getpid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getppid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getpgrp_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getuid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_geteuid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getgid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getegid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getpgid_ocall, + (oe_ocall_func_t) ocall_oe_syscall_getgroups_ocall, + (oe_ocall_func_t) ocall_oe_syscall_uname_ocall, + (oe_ocall_func_t) ocall_oe_get_supported_attester_format_ids_ocall, + (oe_ocall_func_t) ocall_oe_get_qetarget_info_ocall, + (oe_ocall_func_t) ocall_oe_get_quote_ocall, + (oe_ocall_func_t) ocall_oe_get_quote_verification_collateral_ocall, + (oe_ocall_func_t) ocall_oe_verify_quote_ocall, + (oe_ocall_func_t) ocall_oe_sgx_get_cpuid_table_ocall, + (oe_ocall_func_t) ocall_oe_sgx_backtrace_symbols_ocall, + (oe_ocall_func_t) ocall_oe_sgx_thread_wake_wait_ocall, + (oe_ocall_func_t) ocall_oe_sgx_wake_switchless_worker_ocall, + (oe_ocall_func_t) ocall_oe_sgx_sleep_switchless_worker_ocall, + NULL +}; + +oe_result_t oe_create_sgx_cpp_enclave( + const char* path, + oe_enclave_type_t type, + uint32_t flags, + const oe_enclave_setting_t* settings, + uint32_t setting_count, + oe_enclave_t** enclave) +{ + return oe_create_enclave( + path, + type, + flags, + settings, + setting_count, + _sgx_cpp_ocall_function_table, + 83, + _sgx_cpp_ecall_info_table, + 15, + enclave); +} + +OE_EXTERNC_END diff --git a/enclave/sgx_cpp_u.h b/enclave/sgx_cpp_u.h new file mode 100644 index 000000000..9f001874e --- /dev/null +++ b/enclave/sgx_cpp_u.h @@ -0,0 +1,559 @@ +/* + * This file is auto generated by oeedger8r. DO NOT EDIT. + */ +#ifndef EDGER8R_SGX_CPP_U_H +#define EDGER8R_SGX_CPP_U_H + +#include + +#include "sgx_cpp_args.h" + +OE_EXTERNC_BEGIN + +oe_result_t oe_create_sgx_cpp_enclave( + const char* path, + oe_enclave_type_t type, + uint32_t flags, + const oe_enclave_setting_t* settings, + uint32_t setting_count, + oe_enclave_t** enclave); + +/**** ECALL prototypes. ****/ +oe_result_t request_counter( + oe_enclave_t* enclave, + int* _retval, + uint32_t* index); + +oe_result_t get_counter( + oe_enclave_t* enclave, + int* _retval, + uint32_t* index, + size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t* buffer_size_array, + unsigned char** previous_attestation, + uint32_t* counter_value); + +oe_result_t reset_prng( + oe_enclave_t* enclave, + int* _retval, + uint32_t* seed, + uint32_t* range); + +oe_result_t generate_rdrand( + oe_enclave_t* enclave, + int* _retval, + uint32_t* rdrandNum); + +oe_result_t generate_rand( + oe_enclave_t* enclave, + int* _retval, + size_t input_len, + size_t limit_count, + uint32_t* counter_value_array, + size_t* buffer_size_array, + unsigned char** previous_attestation, + uint32_t* randNum); + +oe_result_t generate_key( + oe_enclave_t* enclave, + int* _retval, + size_t* key_size); + +oe_result_t update_key( + oe_enclave_t* enclave, + int* _retval, + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len); + +oe_result_t get_pubkey( + oe_enclave_t* enclave, + int* _retval, + unsigned char** key_buf, + size_t* key_len); + +oe_result_t encrypt( + oe_enclave_t* enclave, + int* _retval, + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len); + +oe_result_t decrypt( + oe_enclave_t* enclave, + int* _retval, + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len); + +oe_result_t oe_get_sgx_report_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + const void* opt_params, + size_t opt_params_size, + sgx_report_t* report); + +oe_result_t oe_get_report_v2_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + uint32_t flags, + const void* opt_params, + size_t opt_params_size, + uint8_t** report_buffer, + size_t* report_buffer_size); + +oe_result_t oe_verify_local_report_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + const uint8_t* report, + size_t report_size, + oe_report_t* parsed_report); + +oe_result_t oe_sgx_init_context_switchless_ecall( + oe_enclave_t* enclave, + oe_result_t* _retval, + oe_host_worker_context_t* host_worker_contexts, + uint64_t num_host_workers); + +oe_result_t oe_sgx_switchless_enclave_worker_thread_ecall( + oe_enclave_t* enclave, + oe_enclave_worker_context_t* context); + +/**** OCALL prototypes. ****/ +oe_host_fd_t oe_syscall_epoll_create1_ocall(int flags); + +int oe_syscall_epoll_wait_ocall( + int64_t epfd, + struct oe_epoll_event* events, + unsigned int maxevents, + int timeout); + +int oe_syscall_epoll_wake_ocall(void); + +int oe_syscall_epoll_ctl_ocall( + int64_t epfd, + int op, + int64_t fd, + struct oe_epoll_event* event); + +int oe_syscall_epoll_close_ocall(oe_host_fd_t epfd); + +oe_host_fd_t oe_syscall_open_ocall( + const char* pathname, + int flags, + oe_mode_t mode); + +ssize_t oe_syscall_read_ocall( + oe_host_fd_t fd, + void* buf, + size_t count); + +ssize_t oe_syscall_write_ocall( + oe_host_fd_t fd, + const void* buf, + size_t count); + +ssize_t oe_syscall_readv_ocall( + oe_host_fd_t fd, + void* iov_buf, + int iovcnt, + size_t iov_buf_size); + +ssize_t oe_syscall_writev_ocall( + oe_host_fd_t fd, + const void* iov_buf, + int iovcnt, + size_t iov_buf_size); + +oe_off_t oe_syscall_lseek_ocall( + oe_host_fd_t fd, + oe_off_t offset, + int whence); + +ssize_t oe_syscall_pread_ocall( + oe_host_fd_t fd, + void* buf, + size_t count, + oe_off_t offset); + +ssize_t oe_syscall_pwrite_ocall( + oe_host_fd_t fd, + const void* buf, + size_t count, + oe_off_t offset); + +int oe_syscall_close_ocall(oe_host_fd_t fd); + +int oe_syscall_flock_ocall( + oe_host_fd_t fd, + int operation); + +int oe_syscall_fsync_ocall(oe_host_fd_t fd); + +int oe_syscall_fdatasync_ocall(oe_host_fd_t fd); + +oe_host_fd_t oe_syscall_dup_ocall(oe_host_fd_t oldfd); + +uint64_t oe_syscall_opendir_ocall(const char* name); + +int oe_syscall_readdir_ocall( + uint64_t dirp, + struct oe_dirent* entry); + +void oe_syscall_rewinddir_ocall(uint64_t dirp); + +int oe_syscall_closedir_ocall(uint64_t dirp); + +int oe_syscall_stat_ocall( + const char* pathname, + struct oe_stat_t* buf); + +int oe_syscall_fstat_ocall( + oe_host_fd_t fd, + struct oe_stat_t* buf); + +int oe_syscall_access_ocall( + const char* pathname, + int mode); + +int oe_syscall_link_ocall( + const char* oldpath, + const char* newpath); + +int oe_syscall_unlink_ocall(const char* pathname); + +int oe_syscall_rename_ocall( + const char* oldpath, + const char* newpath); + +int oe_syscall_truncate_ocall( + const char* path, + oe_off_t length); + +int oe_syscall_ftruncate_ocall( + oe_host_fd_t fd, + oe_off_t length); + +int oe_syscall_mkdir_ocall( + const char* pathname, + oe_mode_t mode); + +int oe_syscall_rmdir_ocall(const char* pathname); + +int oe_syscall_fcntl_ocall( + oe_host_fd_t fd, + int cmd, + uint64_t arg, + uint64_t argsize, + void* argout); + +int oe_syscall_ioctl_ocall( + oe_host_fd_t fd, + uint64_t request, + uint64_t arg, + uint64_t argsize, + void* argout); + +int oe_syscall_poll_ocall( + struct oe_host_pollfd* host_fds, + oe_nfds_t nfds, + int timeout); + +int oe_syscall_kill_ocall( + int pid, + int signum); + +int oe_syscall_close_socket_ocall(oe_host_fd_t sockfd); + +oe_host_fd_t oe_syscall_socket_ocall( + int domain, + int type, + int protocol); + +int oe_syscall_shutdown_sockets_device_ocall(oe_host_fd_t sockfd); + +int oe_syscall_socketpair_ocall( + int domain, + int type, + int protocol, + oe_host_fd_t sv[2]); + +int oe_syscall_connect_ocall( + oe_host_fd_t sockfd, + const struct oe_sockaddr* addr, + oe_socklen_t addrlen); + +oe_host_fd_t oe_syscall_accept_ocall( + oe_host_fd_t sockfd, + struct oe_sockaddr* addr, + oe_socklen_t addrlen_in, + oe_socklen_t* addrlen_out); + +int oe_syscall_bind_ocall( + oe_host_fd_t sockfd, + const struct oe_sockaddr* addr, + oe_socklen_t addrlen); + +int oe_syscall_listen_ocall( + oe_host_fd_t sockfd, + int backlog); + +ssize_t oe_syscall_recvmsg_ocall( + oe_host_fd_t sockfd, + void* msg_name, + oe_socklen_t msg_namelen, + oe_socklen_t* msg_namelen_out, + void* msg_iov_buf, + size_t msg_iovlen, + size_t msg_iov_buf_size, + void* msg_control, + size_t msg_controllen, + size_t* msg_controllen_out, + int flags); + +ssize_t oe_syscall_sendmsg_ocall( + oe_host_fd_t sockfd, + const void* msg_name, + oe_socklen_t msg_namelen, + void* msg_iov_buf, + size_t msg_iovlen, + size_t msg_iov_buf_size, + const void* msg_control, + size_t msg_controllen, + int flags); + +ssize_t oe_syscall_recv_ocall( + oe_host_fd_t sockfd, + void* buf, + size_t len, + int flags); + +ssize_t oe_syscall_recvfrom_ocall( + oe_host_fd_t sockfd, + void* buf, + size_t len, + int flags, + struct oe_sockaddr* src_addr, + oe_socklen_t addrlen_in, + oe_socklen_t* addrlen_out); + +ssize_t oe_syscall_send_ocall( + oe_host_fd_t sockfd, + const void* buf, + size_t len, + int flags); + +ssize_t oe_syscall_sendto_ocall( + oe_host_fd_t sockfd, + const void* buf, + size_t len, + int flags, + const struct oe_sockaddr* dest_addr, + oe_socklen_t addrlen); + +ssize_t oe_syscall_recvv_ocall( + oe_host_fd_t fd, + void* iov_buf, + int iovcnt, + size_t iov_buf_size); + +ssize_t oe_syscall_sendv_ocall( + oe_host_fd_t fd, + const void* iov_buf, + int iovcnt, + size_t iov_buf_size); + +int oe_syscall_shutdown_ocall( + oe_host_fd_t sockfd, + int how); + +int oe_syscall_setsockopt_ocall( + oe_host_fd_t sockfd, + int level, + int optname, + const void* optval, + oe_socklen_t optlen); + +int oe_syscall_getsockopt_ocall( + oe_host_fd_t sockfd, + int level, + int optname, + void* optval, + oe_socklen_t optlen_in, + oe_socklen_t* optlen_out); + +int oe_syscall_getsockname_ocall( + oe_host_fd_t sockfd, + struct oe_sockaddr* addr, + oe_socklen_t addrlen_in, + oe_socklen_t* addrlen_out); + +int oe_syscall_getpeername_ocall( + oe_host_fd_t sockfd, + struct oe_sockaddr* addr, + oe_socklen_t addrlen_in, + oe_socklen_t* addrlen_out); + +int oe_syscall_getaddrinfo_open_ocall( + const char* node, + const char* service, + const struct oe_addrinfo* hints, + uint64_t* handle); + +int oe_syscall_getaddrinfo_read_ocall( + uint64_t handle, + int* ai_flags, + int* ai_family, + int* ai_socktype, + int* ai_protocol, + oe_socklen_t ai_addrlen_in, + oe_socklen_t* ai_addrlen, + struct oe_sockaddr* ai_addr, + size_t ai_canonnamelen_in, + size_t* ai_canonnamelen, + char* ai_canonname); + +int oe_syscall_getaddrinfo_close_ocall(uint64_t handle); + +int oe_syscall_getnameinfo_ocall( + const struct oe_sockaddr* sa, + oe_socklen_t salen, + char* host, + oe_socklen_t hostlen, + char* serv, + oe_socklen_t servlen, + int flags); + +int oe_syscall_nanosleep_ocall( + struct oe_timespec* req, + struct oe_timespec* rem); + +int oe_syscall_clock_nanosleep_ocall( + oe_clockid_t clockid, + int flag, + struct oe_timespec* req, + struct oe_timespec* rem); + +int oe_syscall_getpid_ocall(void); + +int oe_syscall_getppid_ocall(void); + +int oe_syscall_getpgrp_ocall(void); + +unsigned int oe_syscall_getuid_ocall(void); + +unsigned int oe_syscall_geteuid_ocall(void); + +unsigned int oe_syscall_getgid_ocall(void); + +unsigned int oe_syscall_getegid_ocall(void); + +int oe_syscall_getpgid_ocall(int pid); + +int oe_syscall_getgroups_ocall( + size_t size, + unsigned int* list); + +int oe_syscall_uname_ocall(struct oe_utsname* buf); + +oe_result_t oe_get_supported_attester_format_ids_ocall(format_ids_t* format_ids); + +oe_result_t oe_get_qetarget_info_ocall( + const oe_uuid_t* format_id, + const void* opt_params, + size_t opt_params_size, + sgx_target_info_t* target_info); + +oe_result_t oe_get_quote_ocall( + const oe_uuid_t* format_id, + const void* opt_params, + size_t opt_params_size, + const sgx_report_t* sgx_report, + void* quote, + size_t quote_size, + size_t* quote_size_out); + +oe_result_t oe_get_quote_verification_collateral_ocall( + uint8_t fmspc[6], + uint8_t collateral_provider, + void* tcb_info, + size_t tcb_info_size, + size_t* tcb_info_size_out, + void* tcb_info_issuer_chain, + size_t tcb_info_issuer_chain_size, + size_t* tcb_info_issuer_chain_size_out, + void* pck_crl, + size_t pck_crl_size, + size_t* pck_crl_size_out, + void* root_ca_crl, + size_t root_ca_crl_size, + size_t* root_ca_crl_size_out, + void* pck_crl_issuer_chain, + size_t pck_crl_issuer_chain_size, + size_t* pck_crl_issuer_chain_size_out, + void* qe_identity, + size_t qe_identity_size, + size_t* qe_identity_size_out, + void* qe_identity_issuer_chain, + size_t qe_identity_issuer_chain_size, + size_t* qe_identity_issuer_chain_size_out); + +oe_result_t oe_verify_quote_ocall( + const oe_uuid_t* format_id, + const void* opt_params, + size_t opt_params_size, + const void* p_quote, + uint32_t quote_size, + const time_t expiration_check_date, + uint32_t* p_collateral_expiration_status, + uint32_t* p_quote_verification_result, + void* p_qve_report_info, + uint32_t qve_report_info_size, + void* p_supplemental_data, + uint32_t supplemental_data_size, + uint32_t* p_supplemental_data_size_out, + uint32_t collateral_version, + const void* p_tcb_info, + uint32_t tcb_info_size, + const void* p_tcb_info_issuer_chain, + uint32_t tcb_info_issuer_chain_size, + const void* p_pck_crl, + uint32_t pck_crl_size, + const void* p_root_ca_crl, + uint32_t root_ca_crl_size, + const void* p_pck_crl_issuer_chain, + uint32_t pck_crl_issuer_chain_size, + const void* p_qe_identity, + uint32_t qe_identity_size, + const void* p_qe_identity_issuer_chain, + uint32_t qe_identity_issuer_chain_size); + +oe_result_t oe_sgx_get_cpuid_table_ocall( + void* cpuid_table_buffer, + size_t cpuid_table_buffer_size); + +oe_result_t oe_sgx_backtrace_symbols_ocall( + oe_enclave_t* oe_enclave, + const uint64_t* buffer, + size_t size, + void* symbols_buffer, + size_t symbols_buffer_size, + size_t* symbols_buffer_size_out); + +void oe_sgx_thread_wake_wait_ocall( + oe_enclave_t* oe_enclave, + uint64_t waiter_tcs, + uint64_t self_tcs); + +void oe_sgx_wake_switchless_worker_ocall(oe_host_worker_context_t* context); + +void oe_sgx_sleep_switchless_worker_ocall(oe_enclave_worker_context_t* context); + +OE_EXTERNC_END + +#endif // EDGER8R_SGX_CPP_U_H diff --git a/enclave/sgxcode/CMakeLists.txt b/enclave/sgxcode/CMakeLists.txt new file mode 100644 index 000000000..4e76c9d1b --- /dev/null +++ b/enclave/sgxcode/CMakeLists.txt @@ -0,0 +1,56 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +cmake_minimum_required(VERSION 3.11) + +# If the CC environment variable has been specified or if the CMAKE_C_COMPILER +# cmake variable has been passed to cmake, use the C compiler that has been +# specified. Otherwise, prefer clang. Same for C++ compiler. +# This must be done before the `project` command. +if (UNIX) + if (NOT DEFINED ENV{CC} AND NOT DEFINED CMAKE_C_COMPILER) + find_program(CMAKE_C_COMPILER clang-11 clang-10 clang) + endif () + if (NOT DEFINED ENV{CXX} AND NOT DEFINED CMAKE_CXX_COMPILER) + find_program(CMAKE_CXX_COMPILER clang++-11 clang++-10 clang++) + endif () +endif () + +project("sgx code" LANGUAGES C CXX) + +find_package(OpenEnclave CONFIG REQUIRED) + +set(CMAKE_CXX_STANDARD 11) +set(OE_CRYPTO_LIB + mbedtls + CACHE STRING "Crypto library used by enclaves.") + +add_subdirectory(enclave) +add_subdirectory(host) + +# Generate key +add_custom_command( + OUTPUT private.pem public.pem + COMMAND openssl genrsa -out private.pem -3 3072 + COMMAND openssl rsa -in private.pem -pubout -out public.pem) + +# Sign enclave +add_custom_command( + OUTPUT enclave/enclave.signed + DEPENDS enclave enclave/common/file-encryptor.conf private.pem + COMMAND openenclave::oesign sign -e $ -c + ${CMAKE_SOURCE_DIR}/enclave/common/file-encryptor.conf -k private.pem) + +add_custom_target(sign ALL DEPENDS enclave/enclave.signed) + +if ((NOT DEFINED ENV{OE_SIMULATION}) OR (NOT $ENV{OE_SIMULATION})) + add_custom_target( + run + DEPENDS sgxhost sign + COMMAND sgxhost ${CMAKE_BINARY_DIR}/enclave/enclave.signed) +endif () + +add_custom_target( + simulate + DEPENDS sgxhost sign + COMMAND sgxhost ${CMAKE_BINARY_DIR}/enclave/enclave.signed --simulate) diff --git a/enclave/sgxcode/Makefile b/enclave/sgxcode/Makefile new file mode 100644 index 000000000..d2d10d2b0 --- /dev/null +++ b/enclave/sgxcode/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +.PHONY: all build clean run simulate + +OE_CRYPTO_LIB := mbedtls +export OE_CRYPTO_LIB + +all: build + +build: + $(MAKE) -C enclave + $(MAKE) -C host + +clean: + $(MAKE) -C enclave clean + $(MAKE) -C host clean + +run: + host/file-encryptorhost ./enclave/file-encryptorenc.signed + +simulate: + host/file-encryptorhost ./enclave/file-encryptorenc.signed --simulate + diff --git a/enclave/sgxcode/README.md b/enclave/sgxcode/README.md new file mode 100644 index 000000000..473d67e05 --- /dev/null +++ b/enclave/sgxcode/README.md @@ -0,0 +1,138 @@ +# The File-Encryptor Sample + +OE SDK comes with a default crypto support library that supports a [subset of the open sources mbedTLS](https://github.com/openenclave/openenclave/blob/master/docs/MbedtlsSupport.md) library. +This sample demonstrates how to perform simple file cryptographic operations inside an enclave using mbedTLS library. + +It has the following properties: + +- Written in C++ +- Show how to encrypt and decrypt data inside an enclave +- Show how to derive a key from a password string using [PBKDF2](https://en.wikipedia.org/wiki/PBKDF2) +- Use AES mbedTLS API to perform encryption and decryption +- Use the following OE APIs + - mbedtls_aes_setkey_* + - mbedtls_aes_crypt_cbc + - mbedtls_pkcs5_pbkdf2_hmac + - mbedtls_ctr_drbg_random + - mbedtls_entropy_* + - mbedtls_ctr_drbg_* + - mbedtls_sha256_* +- Also runs in OE simulation mode + +## Host application + +This sample is relatively straightforward, It's all about the use of the mbedTLS library. + +![Sample components diagram](diagram.png) + +The host application drives an enclave to perform the following operations: + +1. Create an enclave from the host. + +2. Encrypt a `testfile` into `out.encrypted`. It breaks an input file into 16-byte blocks. + It then sends each block to the enclave for encryption one block after the other until the + very last block is encountered. It makes sure the last block is padded to make it a 16-byte block, + which was required AES-CBC encryption algorithm used by the enclave. + +3. Decrypt the `out.encrypted` file to the `out.decrypted` file. + + The decryption process is a reverse of the encryption except that it provides a encryption header + to the encryptor in the enclave in its `initialize_encryptor` call, which contains a + `encryption_header_t` (defined below), that has encryption metadata for the encryptor + to validate its password and retrieve the encryption key from it. + + In the end, the host makes sure the contents of `testfile` and `out.decrypted` are identical + i.e. that the encryption and the decryption produce the expected result. + +4. Terminate the enclave. + +## Enclave library + +### ECALLs + +There are three ECALLs implemented inside the enclave library: + +### 1. initialize_encryptor + +```c +int initialize_encryptor( + bool encrypt, + const char* password, + size_t password_len, + encryption_header_t* header) +``` + +The bulk of the operations done in this enclave call involve allocating resources and setting up mbedTLS for encryption and decryption operations. + +#### For encryption operation + +It does the following operations to generate `encryption_header_t` information for passing back the host to write into the encrypted file. + +```c +typedef struct _encryption_header +{ + size_t fileDataSize; + unsigned char digest[HASH_VALUE_SIZE_IN_BYTES]; + unsigned char encrypted_key[ENCRYPTION_KEY_SIZE_IN_BYTES]; +} encryption_header_t; +``` + +- Generate a SHA256 digest for the input password, stored in digest field. +- Derive a password key from the input password. +- Produce an encryption key. +- Encrypt the encryption key with the password key, stored in `encrypted_key` field. + +See the following routine for implementation details: + +```c +int ecall_dispatcher::prepare_encryption_header( + encryption_header_t* header, + string password) +``` + +#### For decryption operation + +In decryption, instead of generating `encryption_header_t` information, initialize_encryptor uses the host provided `encryption_header_t` +information to validate the input password and extract encryption key for later decryption operations. + +Here what it does: + +- Check password by comparing `encryption_header_t.digest` with the calculated hash of the input password. +- Derive a password key from the input password. +- Decrypt `encryption_header_t.encrypted_key` with the password key produced above, in preparing for upcoming decryption operations. + +See the following routine for details: + +```c +int ecall_dispatcher::parse_encryption_header( + encryption_header_t* header, + string password) +``` + +#### 2. encrypt_block + +```c +int encrypt_block( + bool encrypt, + unsigned char* input_buf, + unsigned char* output_buf, + size_t size) +``` + +Send a block of data to the enclave for encryption using the configuration setup up by the `initialize_encryptor()` call. + +#### 3. close_encryptor() + +```c +void close_encryptor() +``` + +Free all the resources allocated for this encryptor instance. + +## Build and run + +To build and run this sample, please refer to documentation provided in the main [README file](../README.md#building-the-samples) + +#### Note + +The file-encryptor sample can run under OE simulation mode. \ No newline at end of file diff --git a/enclave/sgxcode/enclave/CMakeLists.txt b/enclave/sgxcode/enclave/CMakeLists.txt new file mode 100644 index 000000000..107491317 --- /dev/null +++ b/enclave/sgxcode/enclave/CMakeLists.txt @@ -0,0 +1,42 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +# Use the edger8r to generate C bindings from the EDL file. +add_custom_command( + OUTPUT sgx_cpp_t.h sgx_cpp_t.c sgx_cpp_args.h + DEPENDS ${CMAKE_SOURCE_DIR}/enclave/sgx_cpp.edl + COMMAND + openenclave::oeedger8r --trusted ${CMAKE_SOURCE_DIR}/enclave/sgx_cpp.edl + --search-path ${OE_INCLUDEDIR} --search-path + ${OE_INCLUDEDIR}/openenclave/edl/sgx) + +set(COUNTER_SRC simulated_counter_src) +set(RANDOM_SRC random_src) +set(DECRYPTOR_SRC decryptor_src) + +add_executable( + enclave common/ecalls.cpp common/dispatcher.cpp + ${COUNTER_SRC}/simulated_counter.cpp + ${RANDOM_SRC}/random.cpp + ${DECRYPTOR_SRC}/decryptor.cpp + ${CMAKE_CURRENT_BINARY_DIR}/sgx_cpp_t.c) +if (WIN32) + maybe_build_using_clangw(enclave) +endif () + +target_compile_definitions(enclave PUBLIC OE_API_VERSION=2) + +target_include_directories( + enclave + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # Needed for #include "../shared.h" + ${CMAKE_CURRENT_BINARY_DIR} ${CMAKE_SOURCE_DIR}) + +if (OE_CRYPTO_LIB STREQUAL "openssl_3") + message(STATUS "openssl_3 enable.") +else() + message(STATUS "openssl_3 not enable. ${OE_CRYPTO_LIB}") +endif () + +target_link_libraries( + enclave openenclave::oeenclave openenclave::oecryptoopenssl + openenclave::oelibcxx) diff --git a/enclave/sgxcode/enclave/Makefile b/enclave/sgxcode/enclave/Makefile new file mode 100644 index 000000000..218fbd02d --- /dev/null +++ b/enclave/sgxcode/enclave/Makefile @@ -0,0 +1,53 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +include ../../config.mk + +ifeq ($(OE_CRYPTO_LIB),openssl_3) + CFLAGS=$(shell pkg-config oeenclave-$(C_COMPILER) --variable=${OE_CRYPTO_LIB}flags) + CXXFLAGS=$(shell pkg-config oeenclave-$(CXX_COMPILER) --variable=${OE_CRYPTO_LIB}flags) +else + CFLAGS=$(shell pkg-config oeenclave-$(C_COMPILER) --cflags) + CXXFLAGS=$(shell pkg-config oeenclave-$(CXX_COMPILER) --cflags) +endif +LDFLAGS=$(shell pkg-config oeenclave-$(CXX_COMPILER) --libs) +INCDIR=$(shell pkg-config oeenclave-$(C_COMPILER) --variable=includedir) +CRYPTO_LDFLAGS=$(shell pkg-config oeenclave-$(COMPILER) --variable=${OE_CRYPTO_LIB}libs) + +CRYPTO_SRC = $(OE_CRYPTO_LIB)_src +CXXINCDIR = -I. -I../ -I../.. +CXXSRCS = common/ecalls.cpp \ + $(CRYPTO_SRC)/encryptor.cpp \ + $(CRYPTO_SRC)/keys.cpp + +# Cover openssl, openssl_symcrypt_fips, and openssl_3 +ifneq (,$(findstring openssl,$(OE_CRYPTO_LIB))) + CRYPTO_SRC = openssl_src +endif + +all: + $(MAKE) build + $(MAKE) keys + $(MAKE) sign + +build: + @ echo "Compilers used: $(CC), $(CXX)" + oeedger8r ../enclave/sgx_cpp.edl --trusted \ + --search-path $(INCDIR) \ + --search-path $(INCDIR)/openenclave/edl/sgx + $(CXX) -g -c $(CXXFLAGS) -DOE_API_VERSION=2 -std=c++11 $(CXXINCDIR) \ + $(CXXSRCS) + $(CC) -g -c $(CFLAGS) -DOE_API_VERSION=2 sgx_cpp_t.c -o sgx_cpp_t.o + $(CXX) -o file-encryptorenc ecalls.o encryptor.o keys.o sgx_cpp_t.o $(LDFLAGS) $(CRYPTO_LDFLAGS) + +sign: + oesign sign -e file-encryptorenc -c common/file-encryptor.conf -k private.pem + +clean: + rm -f file-encryptorenc file-encryptorenc.signed *.o sgx_cpp_t.* sgx_cpp_args.h private.pem public.pem + +keys: + openssl genrsa -out private.pem -3 3072 + openssl rsa -in private.pem -pubout -out public.pem + + diff --git a/enclave/sgxcode/enclave/common/dispatcher.cpp b/enclave/sgxcode/enclave/common/dispatcher.cpp new file mode 100644 index 000000000..1d0e0abd6 --- /dev/null +++ b/enclave/sgxcode/enclave/common/dispatcher.cpp @@ -0,0 +1,7 @@ +#include "common/dispatcher.h" +#include "common/trace.h" + + +ecall_dispatcher::ecall_dispatcher() : counterNum(0) +{ +} diff --git a/enclave/sgxcode/enclave/common/dispatcher.h b/enclave/sgxcode/enclave/common/dispatcher.h new file mode 100644 index 000000000..dd25ab2f8 --- /dev/null +++ b/enclave/sgxcode/enclave/common/dispatcher.h @@ -0,0 +1,87 @@ +// Copyright (c) Open Enclave SDK contributors. +// Licensed under the MIT License. + +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + + +using namespace std; + +class ecall_dispatcher +{ + private: + // counter + uint32_t counterNum; + std::vector counters; + + // random generator + std::random_device rd; // Obtain a random number from hardware + uint32_t seed; + uint32_t range; + std::mt19937 eng; + uint32_t randUseCount; + + // decryptor + unsigned char* pubKeyData; + unsigned char* privKeyData; + int pubLen; + int privLen; + + public: + ecall_dispatcher(); + + // counter + int request_counter(uint32_t* index); + int get_counter( + uint32_t* index, + size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* counter_value); + + // random generator + int reset_prng( + uint32_t* seed, + uint32_t* range); + int generate_rdrand(uint32_t* rdrandNum); + int generate_rand(size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* randNum); + + // decryptor + int generate_key(size_t* key_size); + int update_key( + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len); + int get_pubkey( + unsigned char **key_buf, + size_t *key_len); + int encrypt( + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len); + + int decrypt( + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len); + +}; diff --git a/enclave/sgxcode/enclave/common/ecalls.cpp b/enclave/sgxcode/enclave/common/ecalls.cpp new file mode 100644 index 000000000..2a90cfa6f --- /dev/null +++ b/enclave/sgxcode/enclave/common/ecalls.cpp @@ -0,0 +1,93 @@ +// Copyright (c) Open Enclave SDK contributors. +// Licensed under the MIT License. + +#include + +#include "dispatcher.h" +#include "sgx_cpp_t.h" + +// Declare a static dispatcher object for enabling for better organization +// of enclave-wise global variables +static ecall_dispatcher dispatcher; + +int request_counter( + uint32_t* index) +{ + return dispatcher.request_counter(index); +} + +int get_counter( + uint32_t* index, + size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* counter_value) +{ + return dispatcher.get_counter(index,previous_size,limit_count,counter_value_array,buffer_size_array,previous_attestation,counter_value); +} + +int reset_prng( + uint32_t* seed, + uint32_t* range) +{ + return dispatcher.reset_prng(seed, range); +} + +int generate_rdrand(uint32_t* rdrandNum) +{ + return dispatcher.generate_rdrand(rdrandNum); +} + +int generate_rand(size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* randNum) +{ + return dispatcher.generate_rand(previous_size, limit_count, counter_value_array, buffer_size_array, + previous_attestation, randNum); +} + +int generate_key( + size_t *key_size) +{ + return dispatcher.generate_key(key_size); +} + +int update_key( + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len) +{ + return dispatcher.update_key(priv_key, priv_len, pub_key, pub_len); +} + +int get_pubkey( + unsigned char **key_buf, + size_t *key_len) +{ + return dispatcher.get_pubkey(key_buf, key_len); +} + +int encrypt( + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len) +{ + return dispatcher.encrypt(input_buf, output_buf, input_len, output_len); +} + +int decrypt( + unsigned char* input_buf, + unsigned char** output_buf, + size_t input_len, + size_t* output_len) +{ + return dispatcher.decrypt(input_buf, output_buf, input_len, output_len); +} + diff --git a/enclave/sgxcode/enclave/common/file-encryptor.conf b/enclave/sgxcode/enclave/common/file-encryptor.conf new file mode 100644 index 000000000..10e63351b --- /dev/null +++ b/enclave/sgxcode/enclave/common/file-encryptor.conf @@ -0,0 +1,10 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +# Enclave settings: +Debug=1 +NumHeapPages=2048 +NumStackPages=1024 +NumTCS=2 +ProductID=1 +SecurityVersion=1 diff --git a/enclave/sgxcode/enclave/common/random.h b/enclave/sgxcode/enclave/common/random.h new file mode 100644 index 000000000..e69de29bb diff --git a/enclave/sgxcode/enclave/common/trace.h b/enclave/sgxcode/enclave/common/trace.h new file mode 100644 index 000000000..5d0df7e36 --- /dev/null +++ b/enclave/sgxcode/enclave/common/trace.h @@ -0,0 +1,5 @@ +// Copyright (c) Open Enclave SDK contributors. +// Licensed under the MIT License. + +#define TRACE_ENCLAVE(fmt, ...) \ + printf("Enclave: %s(%d): " fmt "\n", __FILE__, __LINE__, ##__VA_ARGS__) diff --git a/enclave/sgxcode/enclave/decryptor_src/decryptor copy.cpp b/enclave/sgxcode/enclave/decryptor_src/decryptor copy.cpp new file mode 100644 index 000000000..a654ee85c --- /dev/null +++ b/enclave/sgxcode/enclave/decryptor_src/decryptor copy.cpp @@ -0,0 +1,139 @@ +#include "common/dispatcher.h" +#include "common/trace.h" +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Generate an encryption key: this is the key used to encrypt data +int ecall_dispatcher::generate_key(size_t key_size) +{ + TRACE_ENCLAVE("generating encryption key"); + int ret = 0; + + RSA *keypair = RSA_generate_key(key_size, RSA_F4, NULL, NULL); + + BIO *pubBIO = BIO_new(BIO_s_mem()); + BIO *privBIO = BIO_new(BIO_s_mem()); + + PEM_write_bio_RSAPublicKey(pubBIO, keypair); + PEM_write_bio_RSAPrivateKey(privBIO, keypair, NULL, NULL, 0, NULL, NULL); + + pubLen = BIO_pending(pubBIO); + privLen = BIO_pending(privBIO); + + pubKeyData = new unsigned char[pubLen + 1]; + privKeyData = new unsigned char[privLen + 1]; + + BIO_read(pubBIO, pubKeyData, pubLen); + BIO_read(privBIO, privKeyData, privLen); + + pubKeyData[pubLen] = '\0'; + privKeyData[privLen] = '\0'; + + BIO_free_all(pubBIO); + BIO_free_all(privBIO); + +exit: + return ret; +} + +int ecall_dispatcher::update_key( + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len) +{ + delete[] privKeyData; + privKeyData = new unsigned char[priv_len + 1]; + privLen = priv_len; + memcpy(privKeyData, priv_key, priv_len); + + delete[] pubKeyData; + pubKeyData = new unsigned char[pub_len + 1]; + pubLen = pub_len; + memcpy(pubKeyData, pub_key, pub_len); +} + +RSA *load_key_from_memory(unsigned char *keydata, bool public_key) +{ + BIO *bio = BIO_new_mem_buf(keydata, -1); + RSA *key = NULL; + if (public_key) + { + PEM_read_bio_RSAPublicKey(bio, &key, NULL, NULL); + } + else + { + PEM_read_bio_RSAPrivateKey(bio, &key, NULL, NULL); + } + + BIO_free_all(bio); + return key; +} + + +int ecall_dispatcher::encrypt( + unsigned char *input_buf, + unsigned char **output_buf, + size_t input_len, + size_t *output_len) +{ + TRACE_ENCLAVE("encrypting data"); + int ret = 0; + + RSA *pub_key = load_key_from_memory(pubKeyData, true); + *output_buf = new unsigned char[RSA_size(pub_key)]; + + if (*output_buf == nullptr) { + delete[] *output_buf; + return -1; // Memory allocation failed + } + + int encrypt_length = RSA_public_encrypt(input_len, input_buf, *output_buf, pub_key, RSA_PKCS1_PADDING); + + if (encrypt_length == -1) { + TRACE_ENCLAVE("encrypting data failed"); + delete[] *output_buf; + return -1; // Encryption failed + } + + *output_len = encrypt_length; + + return ret; // success +} + + +int ecall_dispatcher::decrypt( + unsigned char *input_buf, + unsigned char **output_buf, + size_t input_len, + size_t *output_len) +{ + TRACE_ENCLAVE("decrypting data"); + int ret = 0; + + RSA *priv_key = load_key_from_memory(privKeyData, false); + *output_buf = new unsigned char[RSA_size(priv_key)]; + + if (*output_buf == nullptr) { + delete[] *output_buf; + return -1; // Memory allocation failed + } + + int decrypt_length = RSA_private_decrypt(input_len, input_buf, *output_buf, priv_key, RSA_PKCS1_PADDING); + + if (decrypt_length == -1) { + delete[] *output_buf; + return -1; // Encryption failed + } + + *output_len = decrypt_length; + + return ret; // success +} + diff --git a/enclave/sgxcode/enclave/decryptor_src/decryptor.cpp b/enclave/sgxcode/enclave/decryptor_src/decryptor.cpp new file mode 100644 index 000000000..5e239d910 --- /dev/null +++ b/enclave/sgxcode/enclave/decryptor_src/decryptor.cpp @@ -0,0 +1,224 @@ +#include "common/dispatcher.h" +#include "common/trace.h" +#include +#include +#include +#include +#include +#include + +using namespace std; + +// Generate an encryption key: this is the key used to encrypt data +int ecall_dispatcher::generate_key(size_t* key_size) +{ + TRACE_ENCLAVE("generating encryption key 1"); + ERR_load_crypto_strings(); + int ret = 0; + +/* + RSA *keypair = RSA_generate_key(*key_size, RSA_F4, NULL, NULL); + + BIO *pubBIO = BIO_new(BIO_s_mem()); + BIO *privBIO = BIO_new(BIO_s_mem()); + + PEM_write_bio_RSAPublicKey(pubBIO, keypair); + PEM_write_bio_RSAPrivateKey(privBIO, keypair, NULL, NULL, 0, NULL, NULL); + + pubLen = BIO_pending(pubBIO); + privLen = BIO_pending(privBIO); + + pubKeyData = new unsigned char[pubLen + 1]; + privKeyData = new unsigned char[privLen + 1]; + + BIO_read(pubBIO, pubKeyData, pubLen); + BIO_read(privBIO, privKeyData, privLen); + + pubKeyData[pubLen] = '\0'; + privKeyData[privLen] = '\0'; + + pubLen = BIO_pending(pubBIO); + privLen = BIO_pending(privBIO); + + BIO_free_all(pubBIO); + BIO_free_all(privBIO); +*/ + +{ + size_t pubLen_ = 426; + size_t privLen_ = 1679; + std::string pubKeyDataStr = R"(-----BEGIN RSA PUBLIC KEY----- +sample public key +-----END RSA PUBLIC KEY-----)"; + std::string privKeyDataStr = R"(-----BEGIN RSA PRIVATE KEY----- +sample private key +-----END RSA PRIVATE KEY-----)"; + + pubKeyData = new unsigned char[pubKeyDataStr.size() + 1]; + privKeyData = new unsigned char[privKeyDataStr.size() + 1]; + + std::memcpy(pubKeyData, pubKeyDataStr.c_str(), pubKeyDataStr.size() + 1); // 包括 null terminator + std::memcpy(privKeyData, privKeyDataStr.c_str(), privKeyDataStr.size() + 1); + + pubLen = pubKeyDataStr.size() + 1; + privLen = privKeyDataStr.size() + 1; +} + + TRACE_ENCLAVE("pubKeyData: \n%s",pubKeyData); + TRACE_ENCLAVE("privKeyData: \n%s",privKeyData); + TRACE_ENCLAVE("pubLen: %d",pubLen); + TRACE_ENCLAVE("privLen: %d",privLen); + + return ret; +} + +int ecall_dispatcher::update_key( + unsigned char* priv_key, + size_t priv_len, + unsigned char* pub_key, + size_t pub_len) +{ + delete[] privKeyData; + privKeyData = new unsigned char[priv_len + 1]; + privLen = priv_len; + memcpy(privKeyData, priv_key, priv_len); + + delete[] pubKeyData; + pubKeyData = new unsigned char[pub_len + 1]; + pubLen = pub_len; + memcpy(pubKeyData, pub_key, pub_len); +} + +RSA *load_key_from_memory(unsigned char *keydata, bool public_key) +{ + BIO *bio = BIO_new_mem_buf(keydata, -1); + RSA *key = NULL; + if (public_key) + { + PEM_read_bio_RSAPublicKey(bio, &key, NULL, NULL); + } + else + { + PEM_read_bio_RSAPrivateKey(bio, &key, NULL, NULL); + } + + BIO_free_all(bio); + return key; +} + +int ecall_dispatcher::get_pubkey( + unsigned char **key_buf, + size_t *key_len) +{ + TRACE_ENCLAVE("get_pubkey: \n%s",pubKeyData); + int ret = 0; + *key_buf = new unsigned char[pubLen + 1]; + std::memcpy(*key_buf, pubKeyData, pubLen); + (*key_buf)[pubLen] = '\0'; + *key_len = pubLen; + return ret; +} + + +int ecall_dispatcher::encrypt( + unsigned char *input_buf, + unsigned char **output_buf, + size_t input_len, + size_t *output_len) +{ + TRACE_ENCLAVE("encrypting data"); + int ret = 0; + + RSA *pub_key = load_key_from_memory(pubKeyData, true); + *output_buf = new unsigned char[RSA_size(pub_key)]; + + if (*output_buf == nullptr) { + delete[] *output_buf; + return -1; // Memory allocation failed + } + + int encrypt_length = RSA_public_encrypt(input_len, input_buf, *output_buf, pub_key, RSA_PKCS1_PADDING); + + if (encrypt_length == -1) { + TRACE_ENCLAVE("encrypting data failed"); + delete[] *output_buf; + return -1; // Encryption failed + } + + *output_len = encrypt_length; + + return ret; // success +} + + +// int ecall_dispatcher::decrypt( +// unsigned char *input_buf, +// unsigned char **output_buf, +// size_t input_len, +// size_t *output_len) +// { +// int ret = 0; + +// RSA *priv_key = load_key_from_memory(privKeyData, false); +// *output_buf = new unsigned char[RSA_size(priv_key)]; + +// if (*output_buf == nullptr) { +// delete[] *output_buf; +// return -1; // Memory allocation failed +// } + +// int decrypt_length = RSA_private_decrypt(input_len, input_buf, *output_buf, priv_key, RSA_PKCS1_PADDING); + +// if (decrypt_length == -1) { +// delete[] *output_buf; +// return -1; // Encryption failed +// } + +// *output_len = decrypt_length; + +// return ret; // succeed +// } + +int ecall_dispatcher::decrypt( + unsigned char *input_buf, + unsigned char **output_buf, + size_t input_len, + size_t *output_len) +{ + int ret = 0; + + RSA *priv_key = load_key_from_memory(privKeyData, false); + if (priv_key == nullptr) { + TRACE_ENCLAVE("Failed to load private key."); + return -1; + } + + int key_size = RSA_size(priv_key); + *output_buf = new unsigned char[key_size]; + if (*output_buf == nullptr) { + TRACE_ENCLAVE("Memory allocation failed."); + RSA_free(priv_key); + return -1; + } + + int decrypt_length = RSA_private_decrypt( + input_len, + input_buf, + *output_buf, + priv_key, + RSA_PKCS1_PADDING); + + if (decrypt_length == -1) { + unsigned long err = ERR_get_error(); + char err_msg[120]; + ERR_error_string(err, err_msg); + TRACE_ENCLAVE("Decryption failed: %s",err_msg); + delete[] *output_buf; + RSA_free(priv_key); + return -1; // Decryption failed + } + + *output_len = decrypt_length; + RSA_free(priv_key); + return ret; // Success +} diff --git a/enclave/sgxcode/enclave/random_src/random.cpp b/enclave/sgxcode/enclave/random_src/random.cpp new file mode 100644 index 000000000..d33020f7b --- /dev/null +++ b/enclave/sgxcode/enclave/random_src/random.cpp @@ -0,0 +1,63 @@ +#include "common/dispatcher.h" +#include "common/trace.h" + +int ecall_dispatcher::reset_prng(uint32_t* seed, uint32_t* range) { + int ret = 0; + // TRACE_ENCLAVE("ecall_dispatcher::set_seed"); + + this->seed = *seed; + this->range = *range; + eng.seed(*seed); + this->randUseCount = 0; + +exit: + return ret; +} + +int ecall_dispatcher::generate_rdrand(uint32_t* rdrandNum) { + int ret = 0; + // TRACE_ENCLAVE("ecall_dispatcher::generate_rdrand"); + + *rdrandNum = rd(); + +exit: + return ret; +} + +int ecall_dispatcher::generate_rand(size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* randNum) { + int ret = 0; + // TRACE_ENCLAVE("ecall_dispatcher::generate_rdrand"); + + // TRACE_ENCLAVE("counter_value:%d, randUseCount:%d, generate_rand",counter_value_array[0],randUseCount); + + if (randUseCount != 0 && previous_size < limit_count) { + TRACE_ENCLAVE("%d,%d: Insufficient previous cert number",previous_size,limit_count); + goto exit; + } + + // Check previous counter info and attestation here + for (size_t i = 0; i < previous_size; i++) + { + if (!std::strncmp((const char*)previous_attestation[i], "Fake Attestation", buffer_size_array[i]) == 0) { + TRACE_ENCLAVE("Could not verify the attestation"); + goto exit; + } + if (counter_value_array[i]/3 + 1 != randUseCount) { + TRACE_ENCLAVE("counter_value:%d, randUseCount:%d, Counter value invalid",counter_value_array[i],randUseCount); + goto exit; + } + // TRACE_ENCLAVE("previous_size: %d, counter: %d, attestation: %.*s", previous_size, + // counter_value_array[i], buffer_size_array[i], previous_attestation[i]); + } + + *randNum = eng() % range; + randUseCount++; + +exit: + return ret; +} diff --git a/enclave/sgxcode/enclave/sgx_cpp.edl b/enclave/sgxcode/enclave/sgx_cpp.edl new file mode 100644 index 000000000..149327118 --- /dev/null +++ b/enclave/sgxcode/enclave/sgx_cpp.edl @@ -0,0 +1,45 @@ +// Copyright (c) Open Enclave SDK contributors. +// Licensed under the MIT License. + +enclave { + from "openenclave/edl/syscall.edl" import *; + from "platform.edl" import *; + + + trusted { + public int request_counter([out] uint32_t* index); + public int get_counter([in] uint32_t* index, + size_t previous_size, + size_t limit_count, + [user_check] uint32_t* counter_value_array, + [user_check] size_t* buffer_size_array, + [user_check] unsigned char** previous_attestation, + [out] uint32_t* counter_value); + + public int reset_prng([in] uint32_t* seed, [in] uint32_t* range); + public int generate_rdrand([out] uint32_t* rdrandNum); + public int generate_rand(size_t input_len, + size_t limit_count, + [user_check] uint32_t* counter_value_array, + [user_check] size_t* buffer_size_array, + [user_check] unsigned char** previous_attestation, + [out] uint32_t* randNum); + + public int generate_key([in] size_t *key_size); + public int update_key([in] unsigned char* priv_key, size_t priv_len, + [in] unsigned char* pub_key, size_t pub_len); + public int get_pubkey([user_check] unsigned char** key_buf, + [out] size_t* key_len); + public int encrypt([user_check] unsigned char* input_buf, + [user_check] unsigned char** output_buf, + size_t input_len, size_t* output_len); + public int decrypt([user_check] unsigned char* input_buf, + [user_check] unsigned char** output_buf, + size_t input_len, size_t* output_len); + }; + + //untrusted { + // no untrusted functions in this sample + //}; +}; + diff --git a/enclave/sgxcode/enclave/simulated_counter_src/simulated_counter.cpp b/enclave/sgxcode/enclave/simulated_counter_src/simulated_counter.cpp new file mode 100644 index 000000000..351fba954 --- /dev/null +++ b/enclave/sgxcode/enclave/simulated_counter_src/simulated_counter.cpp @@ -0,0 +1,59 @@ +#include "common/dispatcher.h" +#include "common/trace.h" +#include + +int ecall_dispatcher::request_counter(uint32_t* index) { + int ret = 0; + // TRACE_ENCLAVE("ecall_dispatcher::request_counter"); + + counters.push_back(0); + *index = counterNum; + counterNum++; + +exit: + return ret; +} + +int ecall_dispatcher::get_counter( + uint32_t* index, + size_t previous_size, + size_t limit_count, + uint32_t* counter_value_array, + size_t * buffer_size_array, + unsigned char ** previous_attestation, + uint32_t* counter_value) { + + int ret = 0; + + if (counters[*index]!=0 && previous_size < limit_count) { + TRACE_ENCLAVE("%d,%d: Insufficient previous cert number",previous_size,limit_count); + goto exit; + } + + // Check previous counter info and attestation here + for (size_t i = 0; i < previous_size; i++) + { + if (!std::strncmp((const char*)previous_attestation[i], "Fake Attestation", buffer_size_array[i]) == 0) { + TRACE_ENCLAVE("Could not verify the attestation"); + goto exit; + } + if (counter_value_array[i] != counters[*index] - 1) { + TRACE_ENCLAVE("Counter value invalid"); + goto exit; + } + // TRACE_ENCLAVE("previous_size: %d, counter: %d, attestation: %.*s", previous_size, + // counter_value_array[i], buffer_size_array[i], previous_attestation[i]); + } + // */ + + if (*index < 0 || *index >= counterNum) { + // TRACE_ENCLAVE("Counter index out of range"); + goto exit; + } + + *counter_value = counters[*index]; + counters[*index]++; + +exit: + return ret; +} diff --git a/enclave/sgxcode/host/CMakeLists.txt b/enclave/sgxcode/host/CMakeLists.txt new file mode 100644 index 000000000..b5faeed2c --- /dev/null +++ b/enclave/sgxcode/host/CMakeLists.txt @@ -0,0 +1,25 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +add_custom_command( + OUTPUT sgx_cpp_u.h sgx_cpp_u.c sgx_cpp_args.h + DEPENDS ${CMAKE_SOURCE_DIR}/enclave/sgx_cpp.edl + COMMAND + openenclave::oeedger8r --untrusted ${CMAKE_SOURCE_DIR}/enclave/sgx_cpp.edl + --search-path ${OE_INCLUDEDIR} --search-path + ${OE_INCLUDEDIR}/openenclave/edl/sgx) + +add_executable(sgxhost + host.cpp ${CMAKE_CURRENT_BINARY_DIR}/sgx_cpp_u.c) + +if (WIN32) + copy_oedebugrt_target(sgxhost_oedebugrt) + add_dependencies(sgxhost sgxhost_oedebugrt) +endif () + +target_include_directories( + sgxhost + PRIVATE ${CMAKE_CURRENT_SOURCE_DIR} # Needed for #include "../shared.h" + ${CMAKE_CURRENT_BINARY_DIR}) + +target_link_libraries(sgxhost openenclave::oehost) diff --git a/enclave/sgxcode/host/Makefile b/enclave/sgxcode/host/Makefile new file mode 100644 index 000000000..79719228a --- /dev/null +++ b/enclave/sgxcode/host/Makefile @@ -0,0 +1,24 @@ +# Copyright (c) Open Enclave SDK contributors. +# Licensed under the MIT License. + +include ../../config.mk + +CFLAGS=$(shell pkg-config oehost-$(C_COMPILER) --cflags) +CXXFLAGS=$(shell pkg-config oehost-$(CXX_COMPILER) --cflags) +LDFLAGS=$(shell pkg-config oehost-$(CXX_COMPILER) --libs) +INCDIR=$(shell pkg-config oehost-$(C_COMPILER) --variable=includedir) + +all: build + +build: + @ echo "Compilers used: $(CC), $(CXX)" + oeedger8r ../enclave/sgx_cpp.edl --untrusted \ + --search-path $(INCDIR) \ + --search-path $(INCDIR)/openenclave/edl/sgx + $(CXX) -g -c $(CXXFLAGS) host.cpp + $(CC) -g -c $(CFLAGS) sgx_cpp_u.c + $(CXX) -o file-encryptorhost host.o sgx_cpp_u.o $(LDFLAGS) + +clean: + rm -f file-encryptorhost sgx_cpp_u.* sgx_cpp_args.h *.o ../out.decrypted ../out.encrypted + diff --git a/enclave/sgxcode/host/host.cpp b/enclave/sgxcode/host/host.cpp new file mode 100644 index 000000000..8ff11d940 --- /dev/null +++ b/enclave/sgxcode/host/host.cpp @@ -0,0 +1,278 @@ +// Copyright (c) Open Enclave SDK contributors. +// Licensed under the MIT License. + +// Note: Only for testing enclave ! + +#include +#include +#include +#include +#include +#include "sgx_cpp_u.h" + +using namespace std; + +#define CIPHER_BLOCK_SIZE 16 +#define DATA_BLOCK_SIZE 256 +#define ENCRYPT_OPERATION true +#define DECRYPT_OPERATION false + +oe_enclave_t *enclave = NULL; + +bool check_simulate_opt(int *argc, const char *argv[]) +{ + for (int i = 0; i < *argc; i++) + { + if (strcmp(argv[i], "--simulate") == 0) + { + cout << "Running in simulation mode" << endl; + memmove(&argv[i], &argv[i + 1], (*argc - i) * sizeof(char *)); + (*argc)--; + return true; + } + } + return false; +} + +void printHex(unsigned char* data, size_t length) { + cout << "{"; + for (size_t i = 0; i < length; ++i) { + printf("0x%02x ", data[i]); + } + ::cout << "}" << endl; +} + + +int main(int argc, const char *argv[]) +{ + oe_result_t result; + int ret = 0; + uint32_t flags = OE_ENCLAVE_FLAG_DEBUG; + + if (check_simulate_opt(&argc, argv)) + { + flags |= OE_ENCLAVE_FLAG_SIMULATE; + } + + cout << "Host: enter main" << endl; + if (argc != 2) + { + cerr << "Usage: " << argv[0] + << " enclave_image_path [ --simulate ]" << endl; + return 1; + } + + cout << "Host: create enclave for image:" << argv[1] << endl; + result = oe_create_sgx_cpp_enclave( + argv[1], OE_ENCLAVE_TYPE_SGX, flags, NULL, 0, &enclave); + if (result != OE_OK) + { + cerr << "oe_create_sgx_cpp_enclave() failed with " << argv[0] + << " " << result << endl; + ret = 1; + // goto exit; + } + + // counter test + { + // request a counter + cout << "Host: requesting a counter:" << endl; + uint32_t index = -1; + result = request_counter(enclave, &ret, &index); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: request_counter failed with " << ret << endl; + // goto exit; + } + + // request a counter + cout << "Host: requesting a counter:" << endl; + uint32_t index2 = -1; + result = request_counter(enclave, &ret, &index2); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: request_counter failed with " << ret << endl; + // goto exit; + } + + // get the counter + uint32_t value = -1; + result = get_counter(enclave, &ret, &index, 0, 0, nullptr, nullptr, nullptr, &value); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: get_counter failed with " << ret + << endl; + // goto exit; + } + cout << "Host: get the " << index << "th counter, value: " << value << endl; + + // get the counter + result = get_counter(enclave, &ret, &index2, 0, 0, nullptr, nullptr, nullptr, &value); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: get_counter failed with " << ret + << endl; + // goto exit; + } + cout << "Host: get the " << index2 << "th counter, value: " << value << endl; + + // get the counter + result = get_counter(enclave, &ret, &index, 0, 0, nullptr, nullptr, nullptr, &value); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: get_counter failed with " << ret + << endl; + // goto exit; + } + cout << "Host: get the " << index << "th counter, value: " << value << endl; + } + + // random test + { + // generate random device rand + cout << "Host: generate random device rand:" << endl; + uint32_t rdrandNum = -1; + result = generate_rdrand(enclave, &ret, &rdrandNum); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: reset_prng failed with " << ret << endl; + // goto exit; + } + cout << "Host: get the rdrandNum: " << rdrandNum << endl; + + // reset prng + cout << "Host: reset prng:" << endl; + uint32_t range = 50; + result = reset_prng(enclave, &ret, &rdrandNum, &range); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: reset_prng failed with " << ret << endl; + // goto exit; + } + cout << "Host: reset prng." << endl; + + // generate random device rand + cout << "Host: generate random from prng:" << endl; + uint32_t randNum = -1; + for (size_t i = 0; i < 10; i++) + { + result = generate_rand(enclave, &ret, 0, 0, nullptr, nullptr, nullptr, &randNum); + cout << "Host: get the randNum: " << randNum << endl; + } + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: reset_prng failed with " << ret << endl; + // goto exit; + } + } + +/* + // decryptor test + { + cout << "Host: generate key" << endl; + size_t key_size = 2048; + result = generate_key(enclave, &ret, &key_size); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: generate_key failed with " << ret << endl; + // goto exit; + } + + // Simulating binary data + unsigned char binary_data[] = {0xde, 0xad, 0xbe, 0xef, 0xca, 0xfe, 0xba, 0xbe}; + int data_len = sizeof(binary_data) / sizeof(binary_data[0]); + + unsigned char* key_buf; + size_t key_len = 0; + result = get_pubkey(enclave, &ret, &key_buf, &key_len); + + BIO *bio = BIO_new_mem_buf(key_buf, -1); + RSA *pub_key = NULL; + PEM_read_bio_RSAPublicKey(bio, &pub_key, NULL, NULL); + + size_t encrypted_len = RSA_size(pub_key); + unsigned char* encrypted_data = new unsigned char[encrypted_len]; // Initialize buffer + std::cout<<"Before RSA_public_encrypt."; + size_t result_len = RSA_public_encrypt(data_len, binary_data, encrypted_data, + pub_key, RSA_PKCS1_PADDING); + std::cout<<"After RSA_public_encrypt."; + cout << "Host: encrypt data" << endl; + BIO_free_all(bio); + + cout << "Host: decrypt data" << endl; + unsigned char * decrypted_data; + size_t decrypted_len; + result = decrypt(enclave, &ret, encrypted_data, &decrypted_data, encrypted_len, &decrypted_len); + if (result != OE_OK) + { + ret = 1; + // goto exit; + } + if (ret != 0) + { + cerr << "Host: decrypt failed with " << ret << endl; + // goto exit; + } + + printHex(binary_data, data_len); + printHex(decrypted_data, decrypted_len); + std::cout << "Decrypted data matches original: " << (memcmp(binary_data, decrypted_data, decrypted_len) == 0) << std::endl; + + } +*/ + +exit: + cout << "Host: terminate the enclave" << endl; + cout << "Host: Sample completed successfully." << endl; + oe_terminate_enclave(enclave); + return ret; +} + + +// Command: +// g++ host.cpp /root/code_dev/tee-code/sgx_cpp/build/host/sgx_cpp_u.c -I/root/code_dev/tee-code/sgx_cpp/build/host -I/opt/openenclave_0_17_0/include/ -L/opt/openenclave_0_17_0/lib/openenclave/host/ -loehost -lcrypto -ldl -lpthread diff --git a/entrypoint.sh b/entrypoint.sh new file mode 100755 index 000000000..a5ef6599c --- /dev/null +++ b/entrypoint.sh @@ -0,0 +1,4 @@ +#!/bin/bash + +./service/tools/kv/server_tools/start_kv_service.sh +tail -f /dev/null \ No newline at end of file diff --git a/executor/common/custom_query.h b/executor/common/custom_query.h index e574a98dd..2c8bf4a7a 100644 --- a/executor/common/custom_query.h +++ b/executor/common/custom_query.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/common/mock_transaction_manager.h b/executor/common/mock_transaction_manager.h index 86972f662..b4a6ba532 100644 --- a/executor/common/mock_transaction_manager.h +++ b/executor/common/mock_transaction_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/common/transaction_manager.cpp b/executor/common/transaction_manager.cpp index 854715863..74df05c9c 100644 --- a/executor/common/transaction_manager.cpp +++ b/executor/common/transaction_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/common/transaction_manager.h" @@ -41,6 +35,50 @@ std::unique_ptr TransactionManager::ExecuteData( return std::make_unique(); } +std::unique_ptr TransactionManager::ParseData( + const std::string& data) { + return nullptr; +} + +std::unique_ptr>> +TransactionManager::Prepare(const BatchUserRequest& request) { + std::unique_ptr>> + batch_response = std::make_unique< + std::vector>>(); + { + for (auto& sub_request : request.user_requests()) { + std::unique_ptr response = + ParseData(sub_request.request().data()); + batch_response->push_back(std::move(response)); + } + // LOG(ERROR)<<"prepare data size:"< TransactionManager::ExecuteRequest( + const google::protobuf::Message& request) { + return nullptr; +} + +std::vector> TransactionManager::ExecuteBatchData( + const std::vector>& requests) { + // LOG(ERROR)<<"execute data:"<> ret; + { + for (auto& sub_request : requests) { + std::unique_ptr response = ExecuteRequest(*sub_request); + if (response == nullptr) { + response = std::make_unique(); + } + ret.push_back(std::move(response)); + } + } + return ret; +} + + std::unique_ptr TransactionManager::ExecuteBatch( const BatchUserRequest& request) { std::unique_ptr batch_response = diff --git a/executor/common/transaction_manager.h b/executor/common/transaction_manager.h index 7e243f97a..b1d564ac6 100644 --- a/executor/common/transaction_manager.h +++ b/executor/common/transaction_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -46,6 +40,11 @@ class TransactionManager { virtual std::unique_ptr ExecuteBatch( const BatchUserRequest& request); + std::unique_ptr>> + Prepare(const BatchUserRequest& request); + + std::vector> ExecuteBatchData( + const std::vector>& requests); virtual std::unique_ptr ExecuteData(const std::string& request); bool IsOutOfOrder(); @@ -54,6 +53,11 @@ class TransactionManager { virtual Storage* GetStorage() { return nullptr; }; + protected: + virtual std::unique_ptr ParseData( + const std::string& data); + virtual std::unique_ptr ExecuteRequest( + const google::protobuf::Message& request); private: bool is_out_of_order_ = false; bool need_response_ = true; diff --git a/executor/contract/executor/contract_executor.cpp b/executor/contract/executor/contract_executor.cpp index 6e8c994a0..6db35ad71 100644 --- a/executor/contract/executor/contract_executor.cpp +++ b/executor/contract/executor/contract_executor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/executor/contract_executor.h" diff --git a/executor/contract/executor/contract_executor.h b/executor/contract/executor/contract_executor.h index 8c2a50c80..c902e2977 100644 --- a/executor/contract/executor/contract_executor.h +++ b/executor/contract/executor/contract_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/contract/executor/contract_executor_test.cpp b/executor/contract/executor/contract_executor_test.cpp index 5d29333dd..b8b63aec5 100644 --- a/executor/contract/executor/contract_executor_test.cpp +++ b/executor/contract/executor/contract_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/executor/contract_executor.h" diff --git a/executor/contract/manager/address_manager.cpp b/executor/contract/manager/address_manager.cpp index e155d9f83..2a886dac5 100644 --- a/executor/contract/manager/address_manager.cpp +++ b/executor/contract/manager/address_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/manager/address_manager.h" diff --git a/executor/contract/manager/address_manager.h b/executor/contract/manager/address_manager.h index b0446503c..4e6055aad 100644 --- a/executor/contract/manager/address_manager.h +++ b/executor/contract/manager/address_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/contract/manager/address_manager_test.cpp b/executor/contract/manager/address_manager_test.cpp index 08287b622..21fd9044c 100644 --- a/executor/contract/manager/address_manager_test.cpp +++ b/executor/contract/manager/address_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/manager/address_manager.h" diff --git a/executor/contract/manager/contract_manager.cpp b/executor/contract/manager/contract_manager.cpp index 3b8ff11c5..426a0b8a3 100644 --- a/executor/contract/manager/contract_manager.cpp +++ b/executor/contract/manager/contract_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/manager/contract_manager.h" diff --git a/executor/contract/manager/contract_manager.h b/executor/contract/manager/contract_manager.h index b645d7286..7e250aa60 100644 --- a/executor/contract/manager/contract_manager.h +++ b/executor/contract/manager/contract_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/contract/manager/contract_manager_test.cpp b/executor/contract/manager/contract_manager_test.cpp index 7b7cc32db..23746b49a 100644 --- a/executor/contract/manager/contract_manager_test.cpp +++ b/executor/contract/manager/contract_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/contract/manager/contract_manager.h" diff --git a/executor/contract/manager/utils.h b/executor/contract/manager/utils.h index 2d243d66f..ff9548ca3 100644 --- a/executor/contract/manager/utils.h +++ b/executor/contract/manager/utils.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once 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 8d2ecf557..ff375264f 100644 --- a/executor/kv/kv_executor.cpp +++ b/executor/kv/kv_executor.cpp @@ -1,38 +1,80 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/kv/kv_executor.h" #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::ParseData( + const std::string& request) { + std::unique_ptr kv_request = std::make_unique(); + if (!kv_request->ParseFromString(request)) { + LOG(ERROR) << "parse data fail in KVExecutor!"; + return nullptr; + } + return kv_request; +} + +std::unique_ptr KVExecutor::ExecuteRequest( + const google::protobuf::Message& request) { + KVResponse kv_response; + const KVRequest& kv_request = dynamic_cast(request); + // LOG(ERROR)<<"execute request:"; + + if (kv_request.cmd() == KVRequest::SET) { + Set(kv_request.key(), kv_request.value()); + } else if (kv_request.cmd() == KVRequest::GET) { + kv_response.set_value(Get(kv_request.key())); + } else if (kv_request.cmd() == KVRequest::GETALLVALUES) { + 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; +} std::unique_ptr KVExecutor::ExecuteData( const std::string& request) { @@ -40,7 +82,7 @@ std::unique_ptr KVExecutor::ExecuteData( KVResponse kv_response; if (!kv_request.ParseFromString(request)) { - LOG(ERROR) << "parse data fail"; + LOG(ERROR) << "parse data fail in KVExecutor!"; return nullptr; } @@ -48,34 +90,108 @@ std::unique_ptr KVExecutor::ExecuteData( Set(kv_request.key(), kv_request.value()); } else if (kv_request.cmd() == KVRequest::GET) { kv_response.set_value(Get(kv_request.key())); - } else if (kv_request.cmd() == KVRequest::GETVALUES) { - kv_response.set_value(GetValues()); + } else if (kv_request.cmd() == KVRequest::GETALLVALUES) { + 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::GetValues() { 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 58392f03f..fb1bcde4e 100644 --- a/executor/kv/kv_executor.h +++ b/executor/kv/kv_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -29,27 +23,41 @@ #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; + std::unique_ptr ParseData( + const std::string& request) override; + std::unique_ptr ExecuteRequest( + const google::protobuf::Message& kv_request) override; protected: virtual void Set(const std::string& key, const std::string& value); std::string Get(const std::string& key); - std::string GetValues(); + 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 17b32a46b..9bf78dc4c 100644 --- a/executor/kv/kv_executor_test.cpp +++ b/executor/kv/kv_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/kv/kv_executor.h" @@ -28,13 +22,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 +40,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) { @@ -83,9 +80,9 @@ class KVExecutorTest : public Test { return kv_response.value(); } - std::string GetValues() { + std::string GetAllValues() { KVRequest request; - request.set_cmd(KVRequest::GETVALUES); + request.set_cmd(KVRequest::GETALLVALUES); std::string str; if (!request.SerializeToString(&str)) { @@ -123,8 +120,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,33 +237,105 @@ 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_EQ(GetAllValues(), "[]"); + EXPECT_EQ(Set("test_key", "test_value"), 0); + EXPECT_EQ(Get("test_key"), "test_value"); - EXPECT_CALL(*mock_storage_ptr_, GetValue("test_key")) - .WillOnce(Invoke([&](const std::string& key) { - std::string ret = data[key]; - return ret; - })); + // 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]"); +} - EXPECT_CALL(*mock_storage_ptr_, GetAllValues()) - .WillOnce(Return("[]")) - .WillOnce(Return("[test_value]")); +TEST_F(KVExecutorTest, SetValueWithVersion) { + std::map data; - EXPECT_CALL(*mock_storage_ptr_, GetRange("a", "z")) - .WillOnce(Return("[test_value]")); + { + 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(GetValues(), "[]"); - EXPECT_EQ(Set("test_key", "test_value"), 0); - EXPECT_EQ(Get("test_key"), "test_value"); + { + 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)); + } - // GetValues and GetRange may be out of order for in-memory, so we test up to - // 1 key-value pair - EXPECT_EQ(GetValues(), "[test_value]"); - EXPECT_EQ(GetRange("a", "z"), "[test_value]"); + { + 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 diff --git a/executor/utxo/executor/utxo_executor.cpp b/executor/utxo/executor/utxo_executor.cpp index ff84f7ddc..a60e535a1 100644 --- a/executor/utxo/executor/utxo_executor.cpp +++ b/executor/utxo/executor/utxo_executor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/executor/utxo_executor.h" diff --git a/executor/utxo/executor/utxo_executor.h b/executor/utxo/executor/utxo_executor.h index a0b625424..2e9acf8bc 100644 --- a/executor/utxo/executor/utxo_executor.h +++ b/executor/utxo/executor/utxo_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/utxo/executor/utxo_executor_test.cpp b/executor/utxo/executor/utxo_executor_test.cpp index cda4a8396..0de5d69d0 100644 --- a/executor/utxo/executor/utxo_executor_test.cpp +++ b/executor/utxo/executor/utxo_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/executor/utxo_executor.h" diff --git a/executor/utxo/manager/transaction.cpp b/executor/utxo/manager/transaction.cpp index b2c750677..787e20118 100644 --- a/executor/utxo/manager/transaction.cpp +++ b/executor/utxo/manager/transaction.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/transaction.h" diff --git a/executor/utxo/manager/transaction.h b/executor/utxo/manager/transaction.h index 238101af3..556d75b08 100644 --- a/executor/utxo/manager/transaction.h +++ b/executor/utxo/manager/transaction.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/utxo/manager/transaction_test.cpp b/executor/utxo/manager/transaction_test.cpp index 0fe154d78..50e239f0a 100644 --- a/executor/utxo/manager/transaction_test.cpp +++ b/executor/utxo/manager/transaction_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/transaction.h" diff --git a/executor/utxo/manager/tx_mempool.cpp b/executor/utxo/manager/tx_mempool.cpp index 889b1fea6..ef832bc63 100644 --- a/executor/utxo/manager/tx_mempool.cpp +++ b/executor/utxo/manager/tx_mempool.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/tx_mempool.h" diff --git a/executor/utxo/manager/tx_mempool.h b/executor/utxo/manager/tx_mempool.h index 13af92bd5..808361b40 100644 --- a/executor/utxo/manager/tx_mempool.h +++ b/executor/utxo/manager/tx_mempool.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/utxo/manager/tx_mempool_test.cpp b/executor/utxo/manager/tx_mempool_test.cpp index 205ad66c9..a9af05176 100644 --- a/executor/utxo/manager/tx_mempool_test.cpp +++ b/executor/utxo/manager/tx_mempool_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/tx_mempool.h" diff --git a/executor/utxo/manager/wallet.cpp b/executor/utxo/manager/wallet.cpp index b69e7568c..644b35d90 100644 --- a/executor/utxo/manager/wallet.cpp +++ b/executor/utxo/manager/wallet.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/wallet.h" diff --git a/executor/utxo/manager/wallet.h b/executor/utxo/manager/wallet.h index 5ed03c1ac..b9755515f 100644 --- a/executor/utxo/manager/wallet.h +++ b/executor/utxo/manager/wallet.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/executor/utxo/manager/wallet_test.cpp b/executor/utxo/manager/wallet_test.cpp index ebf6e2b35..6e606e207 100644 --- a/executor/utxo/manager/wallet_test.cpp +++ b/executor/utxo/manager/wallet_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "executor/utxo/manager/wallet.h" diff --git a/img/apache-incubator.png b/img/apache-incubator.png new file mode 100644 index 0000000000000000000000000000000000000000..338169e4d0698881d3494d80b16232bb5d77c122 GIT binary patch literal 30207 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
#efQ|z*} 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 QanC}Mmj;7MKbZvnAJyS{jQ{`u literal 0 HcmV?d00001 diff --git a/img/apache-resdb.png b/img/apache-resdb.png new file mode 100644 index 0000000000000000000000000000000000000000..1e2d0a0a9fc5375e7834a5490d85095a873fe80b GIT binary patch literal 98785 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
oVlDywcVB3dd0)hwG@iM7I#0I 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 TKf#K(5YwY4juaj~|L6Y!D_ZJ; literal 0 HcmV?d00001 diff --git a/img/resdb.png b/img/resdb.png new file mode 100644 index 0000000000000000000000000000000000000000..bce6eb40c221f60df2c4f9b97d7935a9b4de2437 GIT binary patch literal 36015 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 literal 0 HcmV?d00001 diff --git a/interface/common/mock_resdb_txn_accessor.h b/interface/common/mock_resdb_txn_accessor.h index c900dba47..3962ba275 100644 --- a/interface/common/mock_resdb_txn_accessor.h +++ b/interface/common/mock_resdb_txn_accessor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/common/resdb_state_accessor.cpp b/interface/common/resdb_state_accessor.cpp index 9dbc847c7..8784f8f18 100644 --- a/interface/common/resdb_state_accessor.cpp +++ b/interface/common/resdb_state_accessor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/common/resdb_state_accessor.h" diff --git a/interface/common/resdb_state_accessor.h b/interface/common/resdb_state_accessor.h index 8ce5dbede..17efc5971 100644 --- a/interface/common/resdb_state_accessor.h +++ b/interface/common/resdb_state_accessor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/common/resdb_state_accessor_test.cpp b/interface/common/resdb_state_accessor_test.cpp index 77e0954f0..66ff594fb 100644 --- a/interface/common/resdb_state_accessor_test.cpp +++ b/interface/common/resdb_state_accessor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/common/resdb_state_accessor.h" diff --git a/interface/common/resdb_txn_accessor.cpp b/interface/common/resdb_txn_accessor.cpp index b6e4fb137..55ef23b43 100644 --- a/interface/common/resdb_txn_accessor.cpp +++ b/interface/common/resdb_txn_accessor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/common/resdb_txn_accessor.h" diff --git a/interface/common/resdb_txn_accessor.h b/interface/common/resdb_txn_accessor.h index 5aa1305b7..9fab8d222 100644 --- a/interface/common/resdb_txn_accessor.h +++ b/interface/common/resdb_txn_accessor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/common/resdb_txn_accessor_test.cpp b/interface/common/resdb_txn_accessor_test.cpp index f7832c2e0..b85e9e50a 100644 --- a/interface/common/resdb_txn_accessor_test.cpp +++ b/interface/common/resdb_txn_accessor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/common/resdb_txn_accessor.h" diff --git a/interface/contract/contract_client.cpp b/interface/contract/contract_client.cpp index a2d944b9e..2d8b2e98e 100644 --- a/interface/contract/contract_client.cpp +++ b/interface/contract/contract_client.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/contract/contract_client.h" diff --git a/interface/contract/contract_client.h b/interface/contract/contract_client.h index 69e2d4052..7b0c5aed3 100644 --- a/interface/contract/contract_client.h +++ b/interface/contract/contract_client.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/kv/kv_client.cpp b/interface/kv/kv_client.cpp index e3a3e7e48..25526395b 100644 --- a/interface/kv/kv_client.cpp +++ b/interface/kv/kv_client.cpp @@ -1,34 +1,26 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/kv/kv_client.h" #include -#include "proto/kv/kv.pb.h" - namespace resdb { KVClient::KVClient(const ResDBConfig& config) @@ -55,9 +47,9 @@ std::unique_ptr KVClient::Get(const std::string& key) { return std::make_unique(response.value()); } -std::unique_ptr KVClient::GetValues() { +std::unique_ptr KVClient::GetAllValues() { KVRequest request; - request.set_cmd(KVRequest::GETVALUES); + request.set_cmd(KVRequest::GETALLVALUES); KVResponse response; int ret = SendRequest(request, &response); if (ret != 0) { @@ -82,4 +74,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 03724d380..52cbcab19 100644 --- a/interface/kv/kv_client.h +++ b/interface/kv/kv_client.h @@ -1,31 +1,26 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once #include "interface/rdbc/transaction_constructor.h" +#include "proto/kv/kv.pb.h" namespace resdb { @@ -34,9 +29,36 @@ 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 GetValues(); + std::unique_ptr GetAllValues(); std::unique_ptr GetRange(const std::string& min_key, const std::string& max_key); }; diff --git a/interface/rdbc/mock_net_channel.h b/interface/rdbc/mock_net_channel.h index 39874da1f..73785c226 100644 --- a/interface/rdbc/mock_net_channel.h +++ b/interface/rdbc/mock_net_channel.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/rdbc/mock_resdb_txn_accessor.h b/interface/rdbc/mock_resdb_txn_accessor.h index 281c05103..f70fa9a31 100644 --- a/interface/rdbc/mock_resdb_txn_accessor.h +++ b/interface/rdbc/mock_resdb_txn_accessor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/rdbc/net_channel.cpp b/interface/rdbc/net_channel.cpp index c4999c4bc..981b0af29 100644 --- a/interface/rdbc/net_channel.cpp +++ b/interface/rdbc/net_channel.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/rdbc/net_channel.h" diff --git a/interface/rdbc/net_channel.h b/interface/rdbc/net_channel.h index 53b1578e2..d1baa692e 100644 --- a/interface/rdbc/net_channel.h +++ b/interface/rdbc/net_channel.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/rdbc/net_channel_test.cpp b/interface/rdbc/net_channel_test.cpp index 1ac81a024..a0da76755 100644 --- a/interface/rdbc/net_channel_test.cpp +++ b/interface/rdbc/net_channel_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/rdbc/net_channel.h" diff --git a/interface/rdbc/transaction_constructor.cpp b/interface/rdbc/transaction_constructor.cpp index f71433484..be42224aa 100644 --- a/interface/rdbc/transaction_constructor.cpp +++ b/interface/rdbc/transaction_constructor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/rdbc/transaction_constructor.h" diff --git a/interface/rdbc/transaction_constructor.h b/interface/rdbc/transaction_constructor.h index c305badf3..39754a846 100644 --- a/interface/rdbc/transaction_constructor.h +++ b/interface/rdbc/transaction_constructor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/interface/rdbc/transaction_constructor_test.cpp b/interface/rdbc/transaction_constructor_test.cpp index 1dda2a895..cd00646d1 100644 --- a/interface/rdbc/transaction_constructor_test.cpp +++ b/interface/rdbc/transaction_constructor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/rdbc/transaction_constructor.h" diff --git a/interface/utxo/utxo_client.cpp b/interface/utxo/utxo_client.cpp index fa68656a3..8ed006f1a 100644 --- a/interface/utxo/utxo_client.cpp +++ b/interface/utxo/utxo_client.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "interface/utxo/utxo_client.h" diff --git a/interface/utxo/utxo_client.h b/interface/utxo/utxo_client.h index b42175452..9cf0d8fce 100644 --- a/interface/utxo/utxo_client.h +++ b/interface/utxo/utxo_client.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/data_comm/data_comm.h b/platform/common/data_comm/data_comm.h index 3a568314d..9f7d48821 100644 --- a/platform/common/data_comm/data_comm.h +++ b/platform/common/data_comm/data_comm.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/data_comm/network_comm.h b/platform/common/data_comm/network_comm.h index 7a4c3f5d2..2eb682992 100644 --- a/platform/common/data_comm/network_comm.h +++ b/platform/common/data_comm/network_comm.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/network/mock_socket.h b/platform/common/network/mock_socket.h index 69172bc4c..008c1f9bc 100644 --- a/platform/common/network/mock_socket.h +++ b/platform/common/network/mock_socket.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/network/network_utils.cpp b/platform/common/network/network_utils.cpp index 0ac783491..24c52beb9 100644 --- a/platform/common/network/network_utils.cpp +++ b/platform/common/network/network_utils.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "network/network_utils.h" diff --git a/platform/common/network/network_utils.h b/platform/common/network/network_utils.h index cd307c3be..512c3054c 100644 --- a/platform/common/network/network_utils.h +++ b/platform/common/network/network_utils.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/network/network_utils_test.cpp b/platform/common/network/network_utils_test.cpp index 967cd03cc..12303c564 100644 --- a/platform/common/network/network_utils_test.cpp +++ b/platform/common/network/network_utils_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "network/network_utils.h" diff --git a/platform/common/network/socket.h b/platform/common/network/socket.h index d6f2df5d9..a0c775338 100644 --- a/platform/common/network/socket.h +++ b/platform/common/network/socket.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/network/tcp_socket.cpp b/platform/common/network/tcp_socket.cpp index bbba6ee1e..b541c71dc 100644 --- a/platform/common/network/tcp_socket.cpp +++ b/platform/common/network/tcp_socket.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/common/network/tcp_socket.h" diff --git a/platform/common/network/tcp_socket.h b/platform/common/network/tcp_socket.h index a84d280cc..b894c41c9 100644 --- a/platform/common/network/tcp_socket.h +++ b/platform/common/network/tcp_socket.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/network/tcp_socket_test.cpp b/platform/common/network/tcp_socket_test.cpp index 3b8311c0d..db5252348 100644 --- a/platform/common/network/tcp_socket_test.cpp +++ b/platform/common/network/tcp_socket_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/common/network/tcp_socket.h" diff --git a/platform/common/queue/batch_queue.h b/platform/common/queue/batch_queue.h index 1657bfe19..ab1cf3ac2 100644 --- a/platform/common/queue/batch_queue.h +++ b/platform/common/queue/batch_queue.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/queue/batch_queue_test.cpp b/platform/common/queue/batch_queue_test.cpp index 519da3da0..378a5f9c3 100644 --- a/platform/common/queue/batch_queue_test.cpp +++ b/platform/common/queue/batch_queue_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/common/queue/batch_queue.h" diff --git a/platform/common/queue/blocking_queue.h b/platform/common/queue/blocking_queue.h index 3dd377528..4cf7acfd7 100644 --- a/platform/common/queue/blocking_queue.h +++ b/platform/common/queue/blocking_queue.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/common/queue/lock_free_queue.h b/platform/common/queue/lock_free_queue.h index 5c1a89aa5..1bbfe249b 100644 --- a/platform/common/queue/lock_free_queue.h +++ b/platform/common/queue/lock_free_queue.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -47,7 +41,7 @@ class LockFreeQueue { bool old_v = true; if (need_notify_.compare_exchange_strong(old_v, false, std::memory_order_acq_rel, - std::memory_order_acq_rel)) { + std::memory_order_acquire)) { std::lock_guard lk(mutex_); cv_.notify_all(); return; diff --git a/platform/common/queue/lock_free_queue_test.cpp b/platform/common/queue/lock_free_queue_test.cpp index a0f6efab6..101d16e25 100644 --- a/platform/common/queue/lock_free_queue_test.cpp +++ b/platform/common/queue/lock_free_queue_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/common/queue/lock_free_queue.h" diff --git a/platform/config/resdb_config.cpp b/platform/config/resdb_config.cpp index d6383f6d3..370f2598b 100644 --- a/platform/config/resdb_config.cpp +++ b/platform/config/resdb_config.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/config/resdb_config.h" @@ -68,7 +62,7 @@ ResDBConfig::ResDBConfig(const ResConfigData& config_data, config_data_.set_view_change_timeout_ms(viewchange_commit_timeout_ms_); } if (config_data_.client_batch_num() == 0) { - config_data_.set_client_batch_num(client_batch_num_); + config_data_.set_client_batch_num(100); } if (config_data_.worker_num() == 0) { config_data_.set_worker_num(worker_num_); @@ -82,8 +76,8 @@ ResDBConfig::ResDBConfig(const ResConfigData& config_data, if (config_data_.tcp_batch_num() == 0) { config_data_.set_tcp_batch_num(100); } - if (!config_data_.has_recovery_enabled()) { - config_data_.set_recovery_enabled(true); + if (config_data_.max_process_txn() == 0) { + config_data_.set_max_process_txn(64); } } @@ -186,7 +180,7 @@ void ResDBConfig::SetSignatureVerifierEnabled(bool enable_sv) { } // Performance setting -bool ResDBConfig::IsPerformanceRunning() { +bool ResDBConfig::IsPerformanceRunning() const { return is_performance_running_ || GetConfigData().is_performance_running(); } @@ -259,4 +253,16 @@ void ResDBConfig::SetViewchangeCommitTimeout(uint64_t timeout_ms) { config_data_.set_view_change_timeout_ms(timeout_ms); } +uint32_t ResDBConfig::GetFailureNum() const { + if (config_data_.failure_num()) { + return config_data_.failure_num(); + } + return failure_num_; +} + +void ResDBConfig::SetFailureNum(uint32_t num) { + config_data_.set_failure_num(num); + failure_num_ = num; +} + } // namespace resdb diff --git a/platform/config/resdb_config.h b/platform/config/resdb_config.h index e4e9f8163..5f223f46e 100644 --- a/platform/config/resdb_config.h +++ b/platform/config/resdb_config.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -100,7 +94,7 @@ class ResDBConfig { void SetSignatureVerifierEnabled(bool enable_sv); // Performance setting - bool IsPerformanceRunning(); + bool IsPerformanceRunning() const; void RunningPerformance(bool); bool IsTestMode() const; @@ -127,6 +121,9 @@ class ResDBConfig { uint32_t GetViewchangeCommitTimeout() const; void SetViewchangeCommitTimeout(uint64_t timeout_ms); + uint32_t GetFailureNum() const; + void SetFailureNum(uint32_t num); + private: ResConfigData config_data_; std::vector replicas_; @@ -141,15 +138,18 @@ class ResDBConfig { bool signature_verifier_enabled_ = true; bool is_performance_running_ = false; bool is_test_mode_ = false; - uint32_t max_process_txn_ = 2048; uint32_t client_batch_wait_time_ms_ = 100; // milliseconds, 0.1s - uint32_t client_batch_num_ = 100; uint64_t viewchange_commit_timeout_ms_ = 60000; // default 60s to change viewchange - uint32_t worker_num_ = 64; - uint32_t input_worker_num_ = 1; - uint32_t output_worker_num_ = 1; + + // This is the default settings. + // change these parameters in the configuration. + uint32_t max_process_txn_ = 64; + uint32_t worker_num_ = 16; + uint32_t input_worker_num_ = 5; + uint32_t output_worker_num_ = 5; + uint32_t failure_num_ = 0; }; } // namespace resdb diff --git a/platform/config/resdb_config_test.cpp b/platform/config/resdb_config_test.cpp index be877c3a3..f480b11f5 100644 --- a/platform/config/resdb_config_test.cpp +++ b/platform/config/resdb_config_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/config/resdb_config.h" diff --git a/platform/config/resdb_config_utils.cpp b/platform/config/resdb_config_utils.cpp index 907bc4854..beae7e957 100644 --- a/platform/config/resdb_config_utils.cpp +++ b/platform/config/resdb_config_utils.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/config/resdb_config_utils.h" diff --git a/platform/config/resdb_config_utils.h b/platform/config/resdb_config_utils.h index aa6974b70..15d77a4bf 100644 --- a/platform/config/resdb_config_utils.h +++ b/platform/config/resdb_config_utils.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/config/resdb_poc_config.cpp b/platform/config/resdb_poc_config.cpp index 8c8fd414a..c644f391a 100644 --- a/platform/config/resdb_poc_config.cpp +++ b/platform/config/resdb_poc_config.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/config/resdb_poc_config.h" diff --git a/platform/config/resdb_poc_config.h b/platform/config/resdb_poc_config.h index e5895f89b..c0c73a5dd 100644 --- a/platform/config/resdb_poc_config.h +++ b/platform/config/resdb_poc_config.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/checkpoint/checkpoint.h b/platform/consensus/checkpoint/checkpoint.h index cd87e67cb..7a5b967ce 100644 --- a/platform/consensus/checkpoint/checkpoint.h +++ b/platform/consensus/checkpoint/checkpoint.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/checkpoint/mock_checkpoint.h b/platform/consensus/checkpoint/mock_checkpoint.h index 57b3f8397..837d895ca 100644 --- a/platform/consensus/checkpoint/mock_checkpoint.h +++ b/platform/consensus/checkpoint/mock_checkpoint.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/duplicate_manager.cpp b/platform/consensus/execution/duplicate_manager.cpp index b2c396fbe..626174b59 100644 --- a/platform/consensus/execution/duplicate_manager.cpp +++ b/platform/consensus/execution/duplicate_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/duplicate_manager.h" diff --git a/platform/consensus/execution/duplicate_manager.h b/platform/consensus/execution/duplicate_manager.h index 69b811cd9..2579196aa 100644 --- a/platform/consensus/execution/duplicate_manager.h +++ b/platform/consensus/execution/duplicate_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/geo_global_executor.cpp b/platform/consensus/execution/geo_global_executor.cpp index 6fd3ef447..1daff7c80 100644 --- a/platform/consensus/execution/geo_global_executor.cpp +++ b/platform/consensus/execution/geo_global_executor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/geo_global_executor.h" diff --git a/platform/consensus/execution/geo_global_executor.h b/platform/consensus/execution/geo_global_executor.h index 7b7f16c89..2a62baefa 100644 --- a/platform/consensus/execution/geo_global_executor.h +++ b/platform/consensus/execution/geo_global_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/geo_global_executor_test.cpp b/platform/consensus/execution/geo_global_executor_test.cpp index 62a80c62a..e911d2aac 100644 --- a/platform/consensus/execution/geo_global_executor_test.cpp +++ b/platform/consensus/execution/geo_global_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/geo_global_executor.h" diff --git a/platform/consensus/execution/geo_transaction_executor.cpp b/platform/consensus/execution/geo_transaction_executor.cpp index c798dd8cc..6b6f2a242 100644 --- a/platform/consensus/execution/geo_transaction_executor.cpp +++ b/platform/consensus/execution/geo_transaction_executor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/geo_transaction_executor.h" diff --git a/platform/consensus/execution/geo_transaction_executor.h b/platform/consensus/execution/geo_transaction_executor.h index c120d3b94..04de32482 100644 --- a/platform/consensus/execution/geo_transaction_executor.h +++ b/platform/consensus/execution/geo_transaction_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/geo_transaction_executor_test.cpp b/platform/consensus/execution/geo_transaction_executor_test.cpp index d263e5232..be7e3acda 100644 --- a/platform/consensus/execution/geo_transaction_executor_test.cpp +++ b/platform/consensus/execution/geo_transaction_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/geo_transaction_executor.h" diff --git a/platform/consensus/execution/mock_geo_global_executor.h b/platform/consensus/execution/mock_geo_global_executor.h index c3676ea52..49e409e3d 100644 --- a/platform/consensus/execution/mock_geo_global_executor.h +++ b/platform/consensus/execution/mock_geo_global_executor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/system_info.cpp b/platform/consensus/execution/system_info.cpp index 38bc502d8..a12740c8e 100644 --- a/platform/consensus/execution/system_info.cpp +++ b/platform/consensus/execution/system_info.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/system_info.h" diff --git a/platform/consensus/execution/system_info.h b/platform/consensus/execution/system_info.h index ecfde2596..069c772f8 100644 --- a/platform/consensus/execution/system_info.h +++ b/platform/consensus/execution/system_info.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/execution/system_info_test.cpp b/platform/consensus/execution/system_info_test.cpp index c2f938167..4ce86c5dc 100644 --- a/platform/consensus/execution/system_info_test.cpp +++ b/platform/consensus/execution/system_info_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/system_info.h" diff --git a/platform/consensus/execution/transaction_executor.cpp b/platform/consensus/execution/transaction_executor.cpp index 55cd6afbc..8c40a9c8f 100644 --- a/platform/consensus/execution/transaction_executor.cpp +++ b/platform/consensus/execution/transaction_executor.cpp @@ -1,31 +1,26 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/transaction_executor.h" #include +#include "common/utils/utils.h" namespace resdb { @@ -41,30 +36,86 @@ TransactionExecutor::TransactionExecutor( execute_queue_("execute"), stop_(false), duplicate_manager_(nullptr) { + + memset(blucket_, 0, sizeof(blucket_)); global_stats_ = Stats::GetGlobalStats(); ordering_thread_ = std::thread(&TransactionExecutor::OrderMessage, this); - execute_thread_ = std::thread(&TransactionExecutor::ExecuteMessage, this); + for (int i = 0; i < execute_thread_num_; ++i) { + execute_thread_.push_back( + std::thread(&TransactionExecutor::ExecuteMessage, this)); + } + + for (int i = 0; i < 1; ++i) { + prepare_thread_.push_back( + std::thread(&TransactionExecutor::PrepareMessage, this)); + } if (transaction_manager_ && transaction_manager_->IsOutOfOrder()) { execute_OOO_thread_ = std::thread(&TransactionExecutor::ExecuteMessageOutOfOrder, this); LOG(ERROR) << " is out of order:" << transaction_manager_->IsOutOfOrder(); } + gc_thread_ = std::thread(&TransactionExecutor::GCProcess, this); } TransactionExecutor::~TransactionExecutor() { Stop(); } +void TransactionExecutor::RegisterExecute(int64_t seq) { + if (execute_thread_num_ == 1) return; + int idx = seq % blucket_num_; + std::unique_lock lk(mutex_); + // LOG(ERROR)<<"register seq:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::milliseconds(10000), [&] { + return ((blucket_[pre_idx] & 2) || !blucket_[pre_idx]); + }); + if ((blucket_[pre_idx] & 2) || !blucket_[pre_idx]) { + break; + } + } + // LOG(ERROR)<<"wait for :"< lk(mutex_); + // LOG(ERROR)<<"finish :"< message) { + global_stats_->IncCommit(); + message->set_commit_time(GetCurrentTime()); + execute_queue_.Push(std::move(message)); +} + void TransactionExecutor::ExecuteMessage() { while (!IsStop()) { auto message = execute_queue_.Pop(); @@ -160,7 +217,11 @@ void TransactionExecutor::ExecuteMessage() { if (transaction_manager_ && transaction_manager_->IsOutOfOrder()) { need_execute = false; } + int64_t start_time = GetCurrentTime(); + global_stats_->AddExecuteQueuingLatency(start_time - message->commit_time()); Execute(std::move(message), need_execute); + int64_t end_time = GetCurrentTime(); + global_stats_->AddExecuteLatency(end_time-start_time); } } @@ -178,7 +239,7 @@ void TransactionExecutor::OnlyExecute(std::unique_ptr request) { // Only Execute the request. BatchUserRequest batch_request; if (!batch_request.ParseFromString(request->data())) { - LOG(ERROR) << "parse data fail"; + LOG(ERROR) << "parse data fail in TransactionExecutor!"; } batch_request.set_seq(request->seq()); batch_request.set_hash(request->hash()); @@ -190,9 +251,6 @@ void TransactionExecutor::OnlyExecute(std::unique_ptr request) { // LOG(INFO) << " get request batch size:" // << batch_request.user_requests_size()<<" proxy // id:"<proxy_id(); - // std::unique_ptr batch_response = - // std::make_unique(); - std::unique_ptr response; if (transaction_manager_) { response = transaction_manager_->ExecuteBatch(batch_request); @@ -204,49 +262,330 @@ void TransactionExecutor::OnlyExecute(std::unique_ptr request) { void TransactionExecutor::Execute(std::unique_ptr request, bool need_execute) { - // Execute the request, then send the response back to the user. - BatchUserRequest batch_request; - if (!batch_request.ParseFromString(request->data())) { - LOG(ERROR) << "parse data fail"; + uint64_t uid = request->uid(); + int64_t seq = request->seq(); + RegisterExecute(request->seq()); + //LOG(ERROR)<<"EXECUTE seq:"< batch_request = nullptr; + std::unique_ptr>> data; + std::vector> * data_p = nullptr; + BatchUserRequest* batch_request_p = nullptr; + + bool need_gc = false; + + if (request->uid() > 0) { + bool current_f = SetFlag(uid, Start_Execute); + if (!current_f) { + global_stats_->ConsumeTransactions(1); + std::unique_ptr> data_f = GetFuture(uid); + //LOG(ERROR)<<"wait prepare:"<get(); + } + //LOG(ERROR)<<"wait prepare done:"< lk(fd_mutex_[uid % mod]); + if(req_[uid % mod][uid] == nullptr){ + LOG(ERROR)<<"data is empty:"<second!=nullptr); + data_p = it->second.get(); + //data = std::move(it->second); + } + int64_t end_time = GetCurrentTime(); + if (end_time - start_time > 1000) { + LOG(ERROR) << "get data done:" << uid + << " wait time:" << (end_time - start_time); + } + } + ClearPromise(uid); + need_gc = true; + } else { + global_stats_->AddNewTransactions(1); + //LOG(ERROR)<<"commit start:"<seq()); - batch_request.set_hash(request->hash()); - batch_request.set_proxy_id(request->proxy_id()); - if (request->has_committed_certs()) { - *batch_request.mutable_committed_certs() = request->committed_certs(); + + // Execute the request, then send the response back to the user. + if (batch_request_p == nullptr) { + batch_request = std::make_unique(); + if (!batch_request->ParseFromString(request->data())) { + LOG(ERROR) << "parse data fail in TransactionExecutor!"; + } + batch_request->set_hash(request->hash()); + if (request->has_committed_certs()) { + *batch_request->mutable_committed_certs() = request->committed_certs(); + } + batch_request->set_seq(request->seq()); + batch_request->set_proxy_id(request->proxy_id()); + batch_request_p = batch_request.get(); + // LOG(ERROR)<<"get data from req:"; + } else { + assert(batch_request_p); + batch_request_p->set_seq(request->seq()); + batch_request_p->set_proxy_id(request->proxy_id()); + // LOG(ERROR)<<" get from cache:"<seq()<<" proxy + // id:"<proxy_id()<<" local id:"<local_id(); // LOG(INFO) << " get request batch size:" - // << batch_request.user_requests_size()<<" proxy - // id:"<proxy_id()<<" need execute:"< batch_response = - // std::make_unique(); + // << batch_request.user_requests_size()<<" proxy id:" + // <proxy_id()<<" need execute:"< response; + // need_execute = false; if (transaction_manager_ && need_execute) { - response = transaction_manager_->ExecuteBatch(batch_request); - } + if (execute_thread_num_ == 1) { + response = transaction_manager_->ExecuteBatch(*batch_request_p); + } else { + std::vector> response_v; + + /* + if (data == nullptr) { + int64_t start_time = GetCurrentTime(); + data = std::move(transaction_manager_->Prepare(*batch_request)); + int64_t end_time = GetCurrentTime(); + if (end_time - start_time > 10) { + // LOG(ERROR)<<"exec data done:"<Prepare(*batch_request_p)); + int64_t end_time = GetCurrentTime(); + if (end_time - start_time > 10) { + // LOG(ERROR)<<"exec data done:"<seq()); + response_v = transaction_manager_->ExecuteBatchData(*data_p); + FinishExecute(request->seq()); - if (duplicate_manager_) { - duplicate_manager_->AddExecuted(batch_request.hash(), batch_request.seq()); + response = std::make_unique(); + for (auto& s : response_v) { + response->add_response()->swap(*s); + } + } } + // LOG(ERROR)<<" CF = :"<<(cf==1)<<" uid:"<IncTotalRequest(batch_request.user_requests_size()); if (response == nullptr) { response = std::make_unique(); } + global_stats_->IncTotalRequest(batch_request_p->user_requests_size()); + response->set_proxy_id(batch_request_p->proxy_id()); + response->set_createtime(batch_request_p->createtime() + request->queuing_time()); + response->set_local_id(batch_request_p->local_id()); + global_stats_->AddCommitDelay(GetCurrentTime()- response->createtime()); + //LOG(ERROR)<<" proxy id:"<proxy_id()<<" local id:"<local_id()<<" latency:"<createtime(); - response->set_createtime(batch_request.createtime()); - response->set_local_id(batch_request.local_id()); - response->set_hash(batch_request.hash()); + response->set_seq(request->seq()); - post_exec_func_(std::move(request), std::move(response)); + if (post_exec_func_) { + post_exec_func_(std::move(request), std::move(response)); + } global_stats_->IncExecuteDone(); + if(need_gc){ + gc_queue_.Push(std::make_unique(uid)); + } } void TransactionExecutor::SetDuplicateManager(DuplicateManager* manager) { duplicate_manager_ = manager; } + +bool TransactionExecutor::SetFlag(uint64_t uid, int f) { + std::unique_lock lk(f_mutex_[uid % mod]); + auto it = flag_[uid % mod].find(uid); + if (it == flag_[uid % mod].end()) { + flag_[uid % mod][uid] |= f; + // LOG(ERROR)<<"NO FUTURE uid:"< lk(f_mutex_[uid % mod]); + auto it = pre_[uid % mod].find(uid); + if (it == pre_[uid % mod].end()) { + return; + } + // LOG(ERROR)<<"CLEAR UID:"<* TransactionExecutor::GetPromise(uint64_t uid) { + std::unique_lock lk(f_mutex_[uid % mod]); + auto it = pre_[uid % mod].find(uid); + if (it == pre_[uid % mod].end()) { + return nullptr; + } + return it->second.get(); +} + +std::unique_ptr> TransactionExecutor::GetFuture(uint64_t uid) { + std::unique_lock lk(f_mutex_[uid % mod]); + auto it = pre_[uid % mod].find(uid); + if (it == pre_[uid % mod].end()) { + return nullptr; + } + //return std::move(it->second); + // LOG(ERROR)<<"add future:"<>(it->second->get_future()); +} + +bool TransactionExecutor::AddFuture(uint64_t uid) { + std::unique_lock lk(f_mutex_[uid % mod]); + auto it = pre_[uid % mod].find(uid); + if (it == pre_[uid % mod].end()) { + // LOG(ERROR)<<"add future:"<> p = + std::make_unique>(); + //auto f = std::make_unique>(p->get_future()); + pre_[uid % mod][uid] = std::move(p); + //pre_f_[uid % mod][uid] = std::move(f); + flag_[uid % mod][uid] = 0; + return true; + } + return false; +} + +void TransactionExecutor::Prepare(std::unique_ptr request) { + if (AddFuture(request->uid())) { + prepare_queue_.Push(std::move(request)); + } +} + +void TransactionExecutor::GCProcess() { + while (!IsStop()) { + std::unique_ptr uid_or = gc_queue_.Pop(); + if (uid_or== nullptr) { + continue; + } + int64_t uid = *uid_or; + + std::vector> * data_p = nullptr; + { + std::unique_lock lk(fd_mutex_[uid % mod]); + assert(data_[uid%mod].find(uid) != data_[uid%mod].end()); + data_p = data_[uid%mod][uid].get(); + } + + for(int i = 0; i < data_p->size(); ++i){ + (*data_p)[i].release(); + } + (*data_p).clear(); + { + std::unique_lock lk(fd_mutex_[uid % mod]); + assert(req_[uid%mod].find(uid) != req_[uid%mod].end()); + assert(data_[uid%mod].find(uid) != data_[uid%mod].end()); + data_[uid%mod].erase(data_[uid%mod].find(uid)); + req_[uid%mod].erase(req_[uid%mod].find(uid)); + } + } +} + +void TransactionExecutor::PrepareMessage() { + while (!IsStop()) { + std::unique_ptr request = prepare_queue_.Pop(); + if (request == nullptr) { + continue; + } + + uint64_t uid = request->uid(); + int current_f = SetFlag(uid, Start_Prepare); + if (current_f == 0) { + // commit has done + // LOG(ERROR)<<" want prepare, commit started:"<* p = GetPromise(uid) ; + assert(p); + //LOG(ERROR)<<" prepare started:"< batch_request = + std::make_unique(); + if (!batch_request->ParseFromString(request->data())) { + LOG(ERROR) << "parse data fail in TransactionExecutor!"; + } + // batch_request = std::make_unique(); + batch_request->set_seq(request->seq()); + batch_request->set_hash(request->hash()); + batch_request->set_proxy_id(request->proxy_id()); + if (request->has_committed_certs()) { + *batch_request->mutable_committed_certs() = request->committed_certs(); + } + + // LOG(ERROR)<<"prepare seq:"<seq()<<" proxy + // id:"<proxy_id()<<" local id:"<local_id(); + + std::unique_ptr>> + request_v = transaction_manager_->Prepare(*batch_request); + { + std::unique_lock lk(fd_mutex_[uid % mod]); + // assert(request_v); + // assert(data_[uid%mod].find(uid) == data_[uid%mod].end()); + data_[uid%mod][uid] = std::move(request_v); + req_[uid % mod][uid] = std::move(batch_request); + } + //LOG(ERROR)<<"set promise:"<set_value(1); + { + int set_ret = SetFlag(uid, End_Prepare); + if (set_ret == 0) { + // LOG(ERROR)<<"commit interrupt:"< message); + Storage* GetStorage(); + void RegisterExecute(int64_t seq); + void WaitForExecute(int64_t seq); + void FinishExecute(int64_t seq); + + void Prepare(std::unique_ptr request); + private: void Execute(std::unique_ptr request, bool need_execute = true); void OnlyExecute(std::unique_ptr request); @@ -86,6 +88,15 @@ class TransactionExecutor { void UpdateMaxExecutedSeq(uint64_t seq); + bool SetFlag(uint64_t uid, int f); + void ClearPromise(uint64_t uid); + void PrepareMessage(); + void GCProcess(); + + bool AddFuture(uint64_t uid); + std::unique_ptr> GetFuture(uint64_t uid); + std::promise* GetPromise(uint64_t uid); + protected: ResDBConfig config_; @@ -97,11 +108,43 @@ class TransactionExecutor { SystemInfo* system_info_ = nullptr; std::unique_ptr transaction_manager_ = nullptr; std::map> candidates_; - std::thread ordering_thread_, execute_thread_, execute_OOO_thread_; + std::thread ordering_thread_, execute_OOO_thread_; + std::vector execute_thread_; LockFreeQueue commit_queue_, execute_queue_, execute_OOO_queue_; std::atomic stop_; Stats* global_stats_ = nullptr; DuplicateManager* duplicate_manager_; + int execute_thread_num_ = 10; + static const int blucket_num_ = 1024; + int blucket_[blucket_num_]; + std::condition_variable cv_; + std::mutex mutex_; + + enum PrepareType { + Start_Prepare = 1, + Start_Execute = 2, + End_Prepare = 4, + }; + + + std::vector prepare_thread_; + std::thread gc_thread_; + static const int mod = 2048; + std::mutex f_mutex_[mod], fd_mutex_[mod]; + LockFreeQueue prepare_queue_; + LockFreeQueue gc_queue_; + typedef std::unique_ptr> PromiseType; + std::map pre_[mod]; + + std::map>> pre_f_[mod]; + std::map flag_[mod]; + + std::map> req_[mod]; + std::unordered_map< + uint64_t, + std::unique_ptr>>> + data_[mod]; + }; } // namespace resdb diff --git a/platform/consensus/execution/transaction_executor_test.cpp b/platform/consensus/execution/transaction_executor_test.cpp index a48aaf6cc..9abb9ac7f 100644 --- a/platform/consensus/execution/transaction_executor_test.cpp +++ b/platform/consensus/execution/transaction_executor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/execution/transaction_executor.h" diff --git a/platform/consensus/ordering/common/algorithm/BUILD b/platform/consensus/ordering/common/algorithm/BUILD new file mode 100644 index 000000000..9abd8715e --- /dev/null +++ b/platform/consensus/ordering/common/algorithm/BUILD @@ -0,0 +1,12 @@ +package(default_visibility = ["//platform/consensus/ordering:__subpackages__"]) + +cc_library( + name = "protocol_base", + srcs = ["protocol_base.cpp"], + hdrs = ["protocol_base.h"], + deps = [ + "//common:comm", + "//common/crypto:signature_verifier", + ], +) + diff --git a/platform/consensus/ordering/common/algorithm/protocol_base.cpp b/platform/consensus/ordering/common/algorithm/protocol_base.cpp new file mode 100644 index 000000000..3c6c2fc3b --- /dev/null +++ b/platform/consensus/ordering/common/algorithm/protocol_base.cpp @@ -0,0 +1,53 @@ +#include "platform/consensus/ordering/common/algorithm/protocol_base.h" + +#include + +namespace resdb { +namespace common { + +ProtocolBase::ProtocolBase( + int id, + int f, + int total_num, + SingleCallFuncType single_call, + BroadcastCallFuncType broadcast_call, + CommitFuncType commit) : + id_(id), + f_(f), + total_num_(total_num), + single_call_(single_call), + broadcast_call_(broadcast_call), + commit_(commit) { + stop_ = false; +} + +ProtocolBase::ProtocolBase( int id, int f, int total_num) : ProtocolBase(id, f, total_num, nullptr, nullptr, nullptr){ + +} + +ProtocolBase::~ProtocolBase() { + Stop(); +} + +void ProtocolBase::Stop(){ + stop_ = true; +} + +bool ProtocolBase::IsStop(){ + return stop_; +} + +int ProtocolBase::SendMessage(int msg_type, const google::protobuf::Message& msg, int node_id) { + return single_call_(msg_type, msg, node_id); +} + +int ProtocolBase::Broadcast(int msg_type, const google::protobuf::Message& msg) { + return broadcast_call_(msg_type, msg); +} + +int ProtocolBase::Commit(const google::protobuf::Message& msg) { + return commit_(msg); +} + +} // namespace protocol +} // namespace resdb diff --git a/platform/consensus/ordering/common/algorithm/protocol_base.h b/platform/consensus/ordering/common/algorithm/protocol_base.h new file mode 100644 index 000000000..a93be8223 --- /dev/null +++ b/platform/consensus/ordering/common/algorithm/protocol_base.h @@ -0,0 +1,64 @@ +#pragma once + +#include +#include +#include "common/crypto/signature_verifier.h" + +namespace resdb { +namespace common { + +class ProtocolBase { + public: + typedef std::function SingleCallFuncType; + typedef std::function BroadcastCallFuncType; + typedef std::function CommitFuncType; + + ProtocolBase( + int id, + int f, + int total_num, + SingleCallFuncType single_call, + BroadcastCallFuncType broadcast_call, + CommitFuncType commit + ); + + ProtocolBase( int id, int f, int total_num); + + + virtual ~ProtocolBase(); + + void Stop(); + + inline + void SetSingleCallFunc(SingleCallFuncType single_call) { single_call_ = single_call; } + + inline + void SetBroadcastCallFunc(BroadcastCallFuncType broadcast_call) { broadcast_call_ = broadcast_call; } + + inline + void SetCommitFunc(CommitFuncType commit_func) { commit_ = commit_func; } + + inline + void SetSignatureVerifier(SignatureVerifier* verifier) { verifier_ = verifier;} + + protected: + int SendMessage(int msg_type, const google::protobuf::Message& msg, int node_id); + int Broadcast(int msg_type, const google::protobuf::Message& msg); + int Commit(const google::protobuf::Message& msg); + + bool IsStop(); + + protected: + int id_; + int f_; + int total_num_; + std::function single_call_; + std::function broadcast_call_; + std::function commit_; + std::atomic stop_; + + SignatureVerifier* verifier_; +}; + +} // namespace protocol +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/BUILD b/platform/consensus/ordering/common/framework/BUILD new file mode 100644 index 000000000..82e03a0fb --- /dev/null +++ b/platform/consensus/ordering/common/framework/BUILD @@ -0,0 +1,49 @@ +package(default_visibility = ["//platform/consensus/ordering:__subpackages__"]) + +cc_library( + name = "consensus", + srcs = ["consensus.cpp"], + hdrs = ["consensus.h"], + deps = [ + ":performance_manager", + ":response_manager", + "//common/utils", + "//executor/common:transaction_manager", + "//platform/consensus/execution:transaction_executor", + "//platform/consensus/ordering/common/algorithm:protocol_base", + "//platform/networkstrate:consensus_manager", + ], +) + +cc_library( + name = "performance_manager", + srcs = ["performance_manager.cpp"], + hdrs = ["performance_manager.h"], + deps = [ + ":transaction_utils", + "//platform/networkstrate:replica_communicator", + "//platform/networkstrate:server_comm", + ], +) + + +cc_library( + name = "response_manager", + srcs = ["response_manager.cpp"], + hdrs = ["response_manager.h"], + deps = [ + ":transaction_utils", + "//platform/networkstrate:replica_communicator", + "//platform/networkstrate:server_comm", + ], +) + +cc_library( + name = "transaction_utils", + srcs = ["transaction_utils.cpp"], + hdrs = ["transaction_utils.h"], + visibility = ["//visibility:public"], + deps = [ + "//platform/proto:resdb_cc_proto", + ], +) diff --git a/platform/consensus/ordering/common/framework/consensus.cpp b/platform/consensus/ordering/common/framework/consensus.cpp new file mode 100644 index 000000000..2566daeb5 --- /dev/null +++ b/platform/consensus/ordering/common/framework/consensus.cpp @@ -0,0 +1,220 @@ +/* + * 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 "platform/consensus/ordering/common/framework/consensus.h" + +#include +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace common { + +Consensus::Consensus(const ResDBConfig& config, + std::unique_ptr executor) + : ConsensusManager(config), + replica_communicator_(GetBroadCastClient()), + transaction_executor_(std::make_unique( + config, + [&](std::unique_ptr request, + std::unique_ptr resp_msg) { + ResponseMsg(*resp_msg); + }, + nullptr, std::move(executor))) { + LOG(INFO) << "is running is performance mode:" + << config_.IsPerformanceRunning(); + is_stop_ = false; + global_stats_ = Stats::GetGlobalStats(); + /* + for(int i = 0; i< 4; ++i){ + send_thread_.push_back(std::thread(&Consensus::AsyncSend, this)); + } + */ +} + +void Consensus::Init(){ + if(performance_manager_ == nullptr){ + performance_manager_ = + config_.IsPerformanceRunning() + ? std::make_unique( + config_, GetBroadCastClient(), GetSignatureVerifier()) + : nullptr; + } + + if(response_manager_ == nullptr){ + response_manager_ = + !config_.IsPerformanceRunning() + ? std::make_unique(config_, GetBroadCastClient(), + GetSignatureVerifier()) + : nullptr; + } +} + +void Consensus::InitProtocol(ProtocolBase * protocol){ + //protocol->SetSignatureVerifier(GetSignatureVerifier()); + + protocol->SetSingleCallFunc( + [&](int type, const google::protobuf::Message& msg, int node_id) { + return SendMsg(type, msg, node_id); + }); + + protocol->SetBroadcastCallFunc( + [&](int type, const google::protobuf::Message& msg) { + return Broadcast(type, msg); + }); + + protocol->SetCommitFunc( + [&](const google::protobuf::Message& msg) { + return CommitMsg(msg); + }); +} + +Consensus::~Consensus(){ + is_stop_ = true; +} + +void Consensus::SetPerformanceManager(std::unique_ptr performance_manager){ + performance_manager_ = std::move(performance_manager); +} + +bool Consensus::IsStop(){ + return is_stop_; +} + +void Consensus::SetupPerformanceDataFunc(std::function func) { + performance_manager_->SetDataFunc(func); +} + +void Consensus::SetCommunicator(ReplicaCommunicator* replica_communicator) { + replica_communicator_ = replica_communicator; +} + +int Consensus::Broadcast(int type, const google::protobuf::Message& msg) { + Request request; + msg.SerializeToString(request.mutable_data()); + request.set_type(Request::TYPE_CUSTOM_CONSENSUS); + request.set_user_type(type); + request.set_sender_id(config_.GetSelfInfo().id()); + + replica_communicator_->BroadCast(request); + return 0; +} + +int Consensus::SendMsg(int type, const google::protobuf::Message& msg, + int node_id) { + Request request; + msg.SerializeToString(request.mutable_data()); + request.set_type(Request::TYPE_CUSTOM_CONSENSUS); + request.set_user_type(type); + request.set_sender_id(config_.GetSelfInfo().id()); + replica_communicator_->SendMessage(request, node_id); + return 0; +} + +std::vector Consensus::GetReplicas() { + return config_.GetReplicaInfos(); +} + +int Consensus::CommitMsg(const google::protobuf::Message &txn) { + return 0; +} + +// The implementation of PBFT. +int Consensus::ConsensusCommit(std::unique_ptr context, + std::unique_ptr request) { + //LOG(ERROR)<<"receive commit:"<type()<<" "<type()); + switch (request->type()) { + case Request::TYPE_CLIENT_REQUEST: + if (config_.IsPerformanceRunning()) { + return performance_manager_->StartEval(); + } + case Request::TYPE_RESPONSE: + if (config_.IsPerformanceRunning()) { + return performance_manager_->ProcessResponseMsg(std::move(context), + std::move(request)); + } + case Request::TYPE_NEW_TXNS: { + return ProcessNewTransaction(std::move(request)); + } + case Request::TYPE_CUSTOM_CONSENSUS: { + return ProcessCustomConsensus(std::move(request)); + } + } + return 0; +} + +int Consensus::ProcessCustomConsensus(std::unique_ptr request) { + return 0; +} + +int Consensus::ProcessNewTransaction(std::unique_ptr request) { + return 0; +} + +int Consensus::ResponseMsg(const BatchUserResponse& batch_resp) { + Request request; + request.set_seq(batch_resp.seq()); + request.set_type(Request::TYPE_RESPONSE); + request.set_sender_id(config_.GetSelfInfo().id()); + request.set_proxy_id(batch_resp.proxy_id()); + batch_resp.SerializeToString(request.mutable_data()); + //global_stats_->AddResponseDelay(GetCurrentTime()- batch_resp.createtime()); + replica_communicator_->SendMessage(request, request.proxy_id()); + return 0; +} +/* +int Consensus::ResponseMsg(const BatchUserResponse& batch_resp) { + if (batch_resp.proxy_id() == 0) { + return 0; + } + resp_queue_.Push(std::make_unique(batch_resp)); + return 0; +} + +void Consensus::AsyncSend() { + while(!IsStop()){ + auto batch_resp = resp_queue_.Pop(); + if(batch_resp == nullptr) { + continue; + } + + //LOG(ERROR)<<"send response:"<seq()); + request.set_type(Request::TYPE_RESPONSE); + request.set_sender_id(config_.GetSelfInfo().id()); + request.set_proxy_id(batch_resp->proxy_id()); + batch_resp->SerializeToString(request.mutable_data()); + global_stats_->AddCommitDelay(GetCurrentTime()- batch_resp->createtime()); + replica_communicator_->SendMessage(request, request.proxy_id()); + } + return ; +} +*/ + +} // namespace common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/consensus.h b/platform/consensus/ordering/common/framework/consensus.h new file mode 100644 index 000000000..881dc72bb --- /dev/null +++ b/platform/consensus/ordering/common/framework/consensus.h @@ -0,0 +1,83 @@ +/* + * 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 "executor/common/transaction_manager.h" +#include "platform/consensus/execution/transaction_executor.h" +#include "platform/consensus/ordering/common/algorithm/protocol_base.h" +#include "platform/consensus/ordering/common/framework/performance_manager.h" +#include "platform/consensus/ordering/common/framework/response_manager.h" +#include "platform/networkstrate/consensus_manager.h" + +namespace resdb { +namespace common { + +class Consensus : public ConsensusManager { + public: + Consensus(const ResDBConfig& config, + std::unique_ptr transaction_manager); + virtual ~Consensus(); + + int ConsensusCommit(std::unique_ptr context, + std::unique_ptr request) override; + std::vector GetReplicas() override; + + void SetupPerformanceDataFunc(std::function func); + + void SetCommunicator(ReplicaCommunicator* replica_communicator); + + void InitProtocol(ProtocolBase * protocol); + + protected: + virtual int ProcessCustomConsensus(std::unique_ptr request); + virtual int ProcessNewTransaction(std::unique_ptr request); + virtual int CommitMsg(const google::protobuf::Message& msg); + + protected: + int SendMsg(int type, const google::protobuf::Message& msg, int node_id); + int Broadcast(int type, const google::protobuf::Message& msg); + int ResponseMsg(const BatchUserResponse& batch_resp); + void AsyncSend(); + bool IsStop(); + + protected: + void Init(); + void SetPerformanceManager(std::unique_ptr performance_manger); + + protected: + ReplicaCommunicator* replica_communicator_; + std::unique_ptr performance_manager_; + std::unique_ptr response_manager_; + std::unique_ptr transaction_executor_; + Stats* global_stats_; + + LockFreeQueue resp_queue_; + std::vector send_thread_; + bool is_stop_; +}; + +} // namespace common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/performance_manager.cpp b/platform/consensus/ordering/common/framework/performance_manager.cpp new file mode 100644 index 000000000..dd09dea7d --- /dev/null +++ b/platform/consensus/ordering/common/framework/performance_manager.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 "platform/consensus/ordering/common/framework/performance_manager.h" + +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace common { + +using comm::CollectorResultCode; + +PerformanceManager::PerformanceManager( + const ResDBConfig& config, ReplicaCommunicator* replica_communicator, + SignatureVerifier* verifier) + : config_(config), + replica_communicator_(replica_communicator), + batch_queue_("user request"), + verifier_(verifier) { + stop_ = false; + eval_started_ = false; + eval_ready_future_ = eval_ready_promise_.get_future(); + if (config_.GetPublicKeyCertificateInfo() + .public_key() + .public_key_info() + .type() == CertificateKeyInfo::CLIENT) { + for (int i = 0; i < 1; ++i) { + user_req_thread_[i] = + std::thread(&PerformanceManager::BatchProposeMsg, this); + } + } + global_stats_ = Stats::GetGlobalStats(); + send_num_ = 0; + total_num_ = 0; + replica_num_ = config_.GetReplicaNum(); + id_ = config_.GetSelfInfo().id(); + primary_ = id_ % replica_num_; + if (primary_ == 0) primary_ = replica_num_; + local_id_ = 1; + sum_ = 0; +} + +PerformanceManager::~PerformanceManager() { + stop_ = true; + for (int i = 0; i < 16; ++i) { + if (user_req_thread_[i].joinable()) { + user_req_thread_[i].join(); + } + } +} + +int PerformanceManager::GetPrimary() { return primary_; } + +std::unique_ptr PerformanceManager::GenerateUserRequest() { + std::unique_ptr request = std::make_unique(); + /* // For transaction decryption + std::string test_data = data_func_(); + request->set_encrypted_data(test_data); + */ + request->set_data(data_func_()); + return request; +} + +void PerformanceManager::SetDataFunc(std::function func) { + data_func_ = std::move(func); +} + +int PerformanceManager::StartEval() { + if (eval_started_) { + return 0; + } + eval_started_ = true; + for (int i = 0; i < 5000000; ++i) { + // if (i%1000000 == 0) { + // LOG(ERROR) << "i: " << i; + // } + + // for (int i = 0; i < 60000000000; ++i) { + std::unique_ptr queue_item = std::make_unique(); + queue_item->context = nullptr; + queue_item->user_request = GenerateUserRequest(); + batch_queue_.Push(std::move(queue_item)); + if (i == 200000) { + eval_ready_promise_.set_value(true); + } + } + LOG(WARNING) << "start eval done"; + return 0; +} + +// =================== response ======================== +// handle the response message. If receive f+1 commit messages, send back to the +// user. +int PerformanceManager::ProcessResponseMsg(std::unique_ptr context, + std::unique_ptr request) { + std::unique_ptr response; + // Add the response message, and use the call back to collect the received + // messages. + // The callback will be triggered if it received f+1 messages. + if (request->ret() == -2) { + // LOG(INFO) << "get response fail:" << request->ret(); + send_num_--; + return 0; + } + + //LOG(INFO) << "get response:" << request->seq() << " sender:"<sender_id(); + std::unique_ptr batch_response = nullptr; + CollectorResultCode ret = + AddResponseMsg(std::move(request), [&](std::unique_ptr request) { + batch_response = std::move(request); + return; + }); + + if (ret == CollectorResultCode::STATE_CHANGED) { + assert(batch_response); + SendResponseToClient(*batch_response); + } + return ret == CollectorResultCode::INVALID ? -2 : 0; +} + +CollectorResultCode PerformanceManager::AddResponseMsg( + std::unique_ptr request, + std::function)> response_call_back) { + if (request == nullptr) { + return CollectorResultCode::INVALID; + } + + //uint64_t seq = request->seq(); + + std::unique_ptr batch_response = std::make_unique(); + if (!batch_response->ParseFromString(request->data())) { + LOG(ERROR) << "parse response fail:"<data().size() + <<" seq:"<seq(); return CollectorResultCode::INVALID; + } + + uint64_t seq = batch_response->local_id(); + //LOG(ERROR)<<"receive seq:"< lk(response_lock_[idx]); + if (response_[idx].find(seq) == response_[idx].end()) { + //LOG(ERROR)<<"has done local seq:"<seq(); + return CollectorResultCode::OK; + } + response_[idx][seq]++; + //LOG(ERROR)<<"get seq :"<seq()<<" local id:"<= config_.GetMinClientReceiveNum()) { + //LOG(ERROR)<<"get seq :"<seq()<<" local id:"< 0) { + uint64_t run_time = GetCurrentTime() - create_time; + //LOG(ERROR)<<"receive current:"<AddLatency(run_time); + } else { + } + //send_num_-=10; + send_num_--; +} + +// =================== request ======================== +int PerformanceManager::BatchProposeMsg() { + LOG(WARNING) << "batch wait time:" << config_.ClientBatchWaitTimeMS() + << " batch num:" << config_.ClientBatchNum() + << " max txn:" << config_.GetMaxProcessTxn(); + std::vector> batch_req; + eval_ready_future_.get(); + bool start = false; + while (!stop_) { + if (send_num_ > config_.GetMaxProcessTxn()) { + // LOG(ERROR)<<"wait send num:"< item = + batch_queue_.Pop(config_.ClientBatchWaitTimeMS()); + if (item == nullptr) { + if(start){ + LOG(ERROR)<<"no data"; + } + continue; + } + batch_req.push_back(std::move(item)); + if (batch_req.size() < config_.ClientBatchNum()) { + continue; + } + } + start = true; + for(int i = 0; i < 1;++i){ + int ret = DoBatch(batch_req); + } + batch_req.clear(); + } + return 0; +} + +int PerformanceManager::DoBatch( + const std::vector>& batch_req) { + auto new_request = comm::NewRequest(Request::TYPE_NEW_TXNS, Request(), + config_.GetSelfInfo().id()); + if (new_request == nullptr) { + return -2; + } + + BatchUserRequest batch_request; + for (size_t i = 0; i < batch_req.size(); ++i) { + BatchUserRequest::UserRequest* req = batch_request.add_user_requests(); + *req->mutable_request() = *batch_req[i]->user_request.get(); + req->set_id(i); + } + + batch_request.set_local_id(local_id_++); + + { + int idx = batch_request.local_id() % response_set_size_; + std::unique_lock lk(response_lock_[idx]); + response_[idx][batch_request.local_id()]++; + } + + batch_request.set_proxy_id(config_.GetSelfInfo().id()); + batch_request.set_createtime(GetCurrentTime()); + batch_request.SerializeToString(new_request->mutable_data()); + if (verifier_) { + auto signature_or = verifier_->SignMessage(new_request->data()); + if (!signature_or.ok()) { + LOG(ERROR) << "Sign message fail"; + return -2; + } + *new_request->mutable_data_signature() = *signature_or; + } + + new_request->set_hash(SignatureVerifier::CalculateHash(new_request->data())); + new_request->set_proxy_id(config_.GetSelfInfo().id()); + new_request->set_user_seq(batch_request.local_id()); + + SendMessage(*new_request); + + global_stats_->BroadCastMsg(); + send_num_++; + sum_ += batch_req.size(); + //LOG(ERROR)<<"send num:"<IncClientCall(); + return 0; +} + +void PerformanceManager::SendMessage(const Request& request){ + replica_communicator_->SendMessage(request, GetPrimary()); +} + +} // namespace common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/performance_manager.h b/platform/consensus/ordering/common/framework/performance_manager.h new file mode 100644 index 000000000..5a874baa9 --- /dev/null +++ b/platform/consensus/ordering/common/framework/performance_manager.h @@ -0,0 +1,103 @@ +/* + * 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 "platform/config/resdb_config.h" +#include "platform/consensus/ordering/common/framework/transaction_utils.h" +#include "platform/networkstrate/replica_communicator.h" +#include "platform/networkstrate/server_comm.h" +#include "platform/statistic/stats.h" + +namespace resdb { +namespace common { + +class PerformanceManager { + public: + PerformanceManager(const ResDBConfig& config, + ReplicaCommunicator* replica_communicator, + SignatureVerifier* verifier); + + virtual ~PerformanceManager(); + + int StartEval(); + + int ProcessResponseMsg(std::unique_ptr context, + std::unique_ptr request); + void SetDataFunc(std::function func); + + protected: + virtual void SendMessage(const Request& request); + + private: + // Add response messages which will be sent back to the caller + // if there are f+1 same messages. + comm::CollectorResultCode AddResponseMsg( + std::unique_ptr request, + std::function)> call_back); + void SendResponseToClient(const BatchUserResponse& batch_response); + + struct QueueItem { + std::unique_ptr context; + std::unique_ptr user_request; + }; + int DoBatch(const std::vector>& batch_req); + int BatchProposeMsg(); + int GetPrimary(); + std::unique_ptr GenerateUserRequest(); + + protected: + ResDBConfig config_; + ReplicaCommunicator* replica_communicator_; + + private: + LockFreeQueue batch_queue_; + std::thread user_req_thread_[16]; + std::atomic stop_; + Stats* global_stats_; + std::atomic send_num_; + std::mutex mutex_; + std::atomic total_num_; + SignatureVerifier* verifier_; + SignatureInfo sig_; + std::function data_func_; + std::future eval_ready_future_; + std::promise eval_ready_promise_; + std::atomic eval_started_; + std::atomic fail_num_; + static const int response_set_size_ = 6000000; + std::map response_[response_set_size_]; + std::mutex response_lock_[response_set_size_]; + int replica_num_; + int id_; + int primary_; + std::atomic local_id_; + std::atomic sum_; +}; + +} // namespace common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/response_manager.cpp b/platform/consensus/ordering/common/framework/response_manager.cpp new file mode 100644 index 000000000..ae6b55cc0 --- /dev/null +++ b/platform/consensus/ordering/common/framework/response_manager.cpp @@ -0,0 +1,242 @@ +/* + * 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 "platform/consensus/ordering/common/framework/response_manager.h" + +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace common { + +using namespace resdb::comm; + +ResponseManager::ResponseManager(const ResDBConfig& config, + ReplicaCommunicator* replica_communicator, + SignatureVerifier* verifier) + : config_(config), + replica_communicator_(replica_communicator), + batch_queue_("user request"), + verifier_(verifier) { + stop_ = false; + local_id_ = 1; + + if (config_.GetPublicKeyCertificateInfo() + .public_key() + .public_key_info() + .type() == CertificateKeyInfo::CLIENT || + config_.IsTestMode()) { + user_req_thread_ = std::thread(&ResponseManager::BatchProposeMsg, this); + } + global_stats_ = Stats::GetGlobalStats(); + send_num_ = 0; +} + +ResponseManager::~ResponseManager() { + stop_ = true; + if (user_req_thread_.joinable()) { + user_req_thread_.join(); + } +} + +// use system info +int ResponseManager::GetPrimary() { return 1; } + +int ResponseManager::NewUserRequest(std::unique_ptr context, + std::unique_ptr user_request) { + context->client = nullptr; + + std::unique_ptr queue_item = std::make_unique(); + queue_item->context = std::move(context); + queue_item->user_request = std::move(user_request); + + batch_queue_.Push(std::move(queue_item)); + return 0; +} + +// =================== response ======================== +// handle the response message. If receive f+1 commit messages, send back to the +// caller. +int ResponseManager::ProcessResponseMsg(std::unique_ptr context, + std::unique_ptr request) { + std::unique_ptr response; + // Add the response message, and use the call back to collect the received + // messages. + // The callback will be triggered if it received f+1 messages. + if (request->ret() == -2) { + LOG(ERROR) << "get response fail:" << request->ret(); + send_num_--; + return 0; + } + CollectorResultCode ret = + AddResponseMsg(std::move(request), [&](const Request& request) { + response = std::make_unique(request); + return; + }); + + if (ret == CollectorResultCode::STATE_CHANGED) { + BatchUserResponse batch_response; + if (batch_response.ParseFromString(response->data())) { + SendResponseToClient(batch_response); + } else { + LOG(ERROR) << "parse response fail:"; + } + } + return ret == CollectorResultCode::INVALID ? -2 : 0; +} + +CollectorResultCode ResponseManager::AddResponseMsg( + std::unique_ptr request, + std::function response_call_back) { + if (request == nullptr) { + return CollectorResultCode::INVALID; + } + + int type = request->type(); + uint64_t seq = request->seq(); + bool done = false; + { + int idx = seq % response_set_size_; + std::unique_lock lk(response_lock_[idx]); + if (response_[idx][seq] == -1) { + return CollectorResultCode::OK; + } + response_[idx][seq]++; + if (response_[idx][seq] >= config_.GetMinClientReceiveNum()) { + response_[idx][seq] = -1; + done = true; + } + } + if (done) { + response_call_back(*request); + return CollectorResultCode::STATE_CHANGED; + } + return CollectorResultCode::OK; +} + +void ResponseManager::SendResponseToClient( + const BatchUserResponse& batch_response) { + uint64_t create_time = batch_response.createtime(); + uint64_t local_id = batch_response.local_id(); + if (create_time > 0) { + uint64_t run_time = GetCurrentTime() - create_time; + global_stats_->AddLatency(run_time); + } else { + LOG(ERROR) << "seq:" << local_id << " no resp"; + } + send_num_--; +} + +// =================== request ======================== +int ResponseManager::BatchProposeMsg() { + LOG(INFO) << "batch wait time:" << config_.ClientBatchWaitTimeMS() + << " batch num:" << config_.ClientBatchNum(); + std::vector> batch_req; + while (!stop_) { + if (send_num_ > config_.GetMaxProcessTxn()) { + LOG(ERROR) << "send num too high, wait:" << send_num_; + usleep(100); + continue; + } + if (batch_req.size() < config_.ClientBatchNum()) { + std::unique_ptr item = + batch_queue_.Pop(config_.ClientBatchWaitTimeMS()); + if (item != nullptr) { + batch_req.push_back(std::move(item)); + if (batch_req.size() < config_.ClientBatchNum()) { + continue; + } + } + } + if (batch_req.empty()) { + continue; + } + int ret = DoBatch(batch_req); + batch_req.clear(); + if (ret != 0) { + Response response; + response.set_result(Response::ERROR); + for (size_t i = 0; i < batch_req.size(); ++i) { + if (batch_req[i]->context && batch_req[i]->context->client) { + int ret = batch_req[i]->context->client->SendRawMessage(response); + if (ret) { + LOG(ERROR) << "send resp" << response.DebugString() + << " fail ret:" << ret; + } + } + } + } + } + return 0; +} + +int ResponseManager::DoBatch( + const std::vector>& batch_req) { + auto new_request = + NewRequest(Request::TYPE_NEW_TXNS, Request(), config_.GetSelfInfo().id()); + if (new_request == nullptr) { + return -2; + } + std::vector> context_list; + + BatchUserRequest batch_request; + for (size_t i = 0; i < batch_req.size(); ++i) { + BatchUserRequest::UserRequest* req = batch_request.add_user_requests(); + *req->mutable_request() = *batch_req[i]->user_request.get(); + *req->mutable_signature() = batch_req[i]->context->signature; + req->set_id(i); + context_list.push_back(std::move(batch_req[i]->context)); + } + + if (!config_.IsPerformanceRunning()) { + LOG(ERROR) << "add context list:" << new_request->seq() + << " list size:" << context_list.size(); + batch_request.set_local_id(local_id_); + } + batch_request.set_createtime(GetCurrentTime()); + std::string data; + batch_request.SerializeToString(&data); + if (verifier_) { + auto signature_or = verifier_->SignMessage(data); + if (!signature_or.ok()) { + LOG(ERROR) << "Sign message fail"; + return -2; + } + *new_request->mutable_data_signature() = *signature_or; + } + + batch_request.SerializeToString(new_request->mutable_data()); + new_request->set_hash(SignatureVerifier::CalculateHash(new_request->data())); + new_request->set_proxy_id(config_.GetSelfInfo().id()); + replica_communicator_->SendMessage(*new_request, GetPrimary()); + send_num_++; + LOG(INFO) << "send msg to primary:" << GetPrimary() + << " batch size:" << batch_req.size(); + return 0; +} + +} // namespace common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/response_manager.h b/platform/consensus/ordering/common/framework/response_manager.h new file mode 100644 index 000000000..1c7043964 --- /dev/null +++ b/platform/consensus/ordering/common/framework/response_manager.h @@ -0,0 +1,85 @@ +/* + * 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 "platform/config/resdb_config.h" +#include "platform/consensus/ordering/common/framework/transaction_utils.h" +#include "platform/networkstrate/replica_communicator.h" +#include "platform/networkstrate/server_comm.h" +#include "platform/statistic/stats.h" + +namespace resdb { +namespace common { + +class ResponseManager { + public: + ResponseManager(const ResDBConfig& config, + ReplicaCommunicator* replica_communicator, + SignatureVerifier* verifier); + + ~ResponseManager(); + + std::vector> FetchContextList(uint64_t id); + + int NewUserRequest(std::unique_ptr context, + std::unique_ptr user_request); + + int ProcessResponseMsg(std::unique_ptr context, + std::unique_ptr request); + + private: + // Add response messages which will be sent back to the caller + // if there are f+1 same messages. + comm::CollectorResultCode AddResponseMsg( + std::unique_ptr request, + std::function call_back); + void SendResponseToClient(const BatchUserResponse& batch_response); + + struct QueueItem { + std::unique_ptr context; + std::unique_ptr user_request; + }; + int DoBatch(const std::vector>& batch_req); + int BatchProposeMsg(); + int GetPrimary(); + + private: + ResDBConfig config_; + ReplicaCommunicator* replica_communicator_; + LockFreeQueue batch_queue_; + std::thread user_req_thread_; + std::atomic stop_; + uint64_t local_id_ = 0; + Stats* global_stats_; + std::atomic send_num_; + SignatureVerifier* verifier_; + static const int response_set_size_ = 6000000; + std::map response_[response_set_size_]; + std::mutex response_lock_[response_set_size_]; +}; + +} // common +} // namespace resdb diff --git a/platform/consensus/ordering/common/framework/transaction_utils.cpp b/platform/consensus/ordering/common/framework/transaction_utils.cpp new file mode 100644 index 000000000..08a8e5444 --- /dev/null +++ b/platform/consensus/ordering/common/framework/transaction_utils.cpp @@ -0,0 +1,49 @@ +/* + * 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 "platform/consensus/ordering/common/framework/transaction_utils.h" + +namespace resdb { +namespace comm { + +std::unique_ptr NewRequest(Request::Type type, const Request& request, + int sender_id) { + auto new_request = std::make_unique(request); + new_request->set_type(type); + new_request->set_sender_id(sender_id); + return new_request; +} + +std::unique_ptr NewRequest(Request::Type type, const Request& request, + int sender_id, int region_id) { + auto new_request = std::make_unique(request); + new_request->set_type(type); + new_request->set_sender_id(sender_id); + new_request->mutable_region_info()->set_region_id(region_id); + return new_request; +} + +} // namespace comm +} // namespace resdb diff --git a/chain/storage/res_rocksdb.h b/platform/consensus/ordering/common/framework/transaction_utils.h similarity index 57% rename from chain/storage/res_rocksdb.h rename to platform/consensus/ordering/common/framework/transaction_utils.h index 706957c2f..3055cf44c 100644 --- a/chain/storage/res_rocksdb.h +++ b/platform/consensus/ordering/common/framework/transaction_utils.h @@ -24,38 +24,22 @@ */ #pragma once - -#include -#include - -#include "chain/storage/storage.h" #include "platform/proto/replica_info.pb.h" -#include "rocksdb/db.h" -#include "rocksdb/write_batch.h" +#include "platform/proto/resdb.pb.h" namespace resdb { +namespace comm { -std::unique_ptr NewResRocksDB( - const char* cert_file, std::optional config_data); - -class ResRocksDB : public Storage { - public: - ResRocksDB(const char* cert_file, std::optional config_data); - virtual ~ResRocksDB(); - int SetValue(const std::string& key, const std::string& value) override; - std::string GetValue(const std::string& key) override; - std::string GetAllValues(void) override; - std::string GetRange(const std::string& min_key, - const std::string& max_key) override; - - bool Flush() override; - - private: - std::unique_ptr db_ = nullptr; - rocksdb::WriteBatch batch_; - unsigned int num_threads_ = 1; - unsigned int write_buffer_size_ = 64 << 20; - unsigned int write_batch_size_ = 1; +enum CollectorResultCode { + INVALID = -2, + OK = 0, + STATE_CHANGED = 1, }; +std::unique_ptr NewRequest(Request::Type type, const Request& request, + int sender_id); + +std::unique_ptr NewRequest(Request::Type type, const Request& request, + int sender_id, int region_info); +} // namespace comm } // namespace resdb diff --git a/platform/consensus/ordering/common/transaction_utils.cpp b/platform/consensus/ordering/common/transaction_utils.cpp index e560cfc9c..fea180a0b 100644 --- a/platform/consensus/ordering/common/transaction_utils.cpp +++ b/platform/consensus/ordering/common/transaction_utils.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/common/transaction_utils.h" diff --git a/platform/consensus/ordering/common/transaction_utils.h b/platform/consensus/ordering/common/transaction_utils.h index b0d6aab29..151b431c3 100644 --- a/platform/consensus/ordering/common/transaction_utils.h +++ b/platform/consensus/ordering/common/transaction_utils.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/fides/algorithm/BUILD b/platform/consensus/ordering/fides/algorithm/BUILD new file mode 100644 index 000000000..adf568035 --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/BUILD @@ -0,0 +1,42 @@ +package(default_visibility = ["//platform/consensus/ordering/fides:__subpackages__"]) + +cc_library( + name = "proposal_manager", + srcs = ["proposal_manager.cpp"], + hdrs = ["proposal_manager.h"], + copts = ["-Iexternal/openenclave"], + deps = [ + "//common:comm", + "//common/crypto:signature_verifier", + "//common/utils", + "//platform/consensus/ordering/fides/proto:proposal_cc_proto", + "//enclave:headers", + ], +) + +cc_library( + name = "fides", + srcs = ["fides.cpp"], + hdrs = ["fides.h"], + copts = ["-Iexternal/openenclave"], + deps = [ + ":proposal_manager", + "//common:comm", + "//common/crypto:signature_verifier", + "//platform/common/queue:lock_free_queue", + "//platform/consensus/ordering/common/algorithm:protocol_base", + "//platform/statistic:stats", + "//enclave:headers", + "//platform/proto:resdb_cc_proto", + "//executor/kv:kv_executor", + ], +) + +cc_library( + name = "transaction_collector", + srcs = ["transaction_collector.cpp"], + hdrs = ["transaction_collector.h"], + deps = [ + "//platform/statistic:stats", + ], +) diff --git a/platform/consensus/ordering/fides/algorithm/fides.cpp b/platform/consensus/ordering/fides/algorithm/fides.cpp new file mode 100644 index 000000000..c28dea0da --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/fides.cpp @@ -0,0 +1,703 @@ +#include "platform/consensus/ordering/fides/algorithm/fides.h" + +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace fides { + +Fides::Fides(int id, int f, int total_num, SignatureVerifier* verifier, const ResDBConfig& config, oe_enclave_t* enclave) + : ProtocolBase(id, f, total_num), verifier_(verifier), config_(config), enclave_(enclave) { + // TODO: 5.Quorum to f+1 - Done + limit_count_ = f + 1; + batch_size_ = 5; + proposal_manager_ = std::make_unique(id, limit_count_, enclave); + + execute_id_ = 1; + start_ = 0; + queue_size_ = 0; + + send_thread_ = std::thread(&Fides::AsyncSend, this); + commit_thread_ = std::thread(&Fides::AsyncCommit, this); + execute_thread_ = std::thread(&Fides::AsyncExecute, this); + cert_thread_ = std::thread(&Fides::AsyncProcessCert, this); + + global_stats_ = Stats::GetGlobalStats(); + + LOG(ERROR) << "id:" << id << " f:" << f << " failureNum:" << config_.GetFailureNum() << " total:" << total_num; +} + +Fides::~Fides() { + if (send_thread_.joinable()) { + send_thread_.join(); + } + if (commit_thread_.joinable()) { + commit_thread_.join(); + } + if(cert_thread_.joinable()){ + cert_thread_.join(); + } +} + +// TODO: 1.Change this to Using trusted global random +// TODO: Not every round has a different leader +int Fides::GetLeader(int r) { + // return r / 2 % total_num_ + 1; + { + std::unique_lock lk(leader_list_mutex_); + if (leader_list_.count(r) != 0) { + return leader_list_[r]; + } + if (r % 3 != 0) { + leader_list_[r] = leader_list_[r-1]; + return leader_list_[r]; + } + } + + int ret; + uint32_t randNum; + + // Send f+1 counter value and attestation to proof can obtain new random number + std::vector counters; + if(r > 0) { + // LOG(ERROR)<<"In GetLeader. round:"<GetCounterFromRound(r - 1); + assert(counters.size()>=limit_count_); + } + + size_t previous_cert_size = counters.size(); + unsigned char** attestation_array = new unsigned char*[previous_cert_size]; + size_t* attestation_size_array = new size_t[previous_cert_size]; + uint32_t* counter_value_array = new uint32_t[previous_cert_size]; + int i = 0; + + std::string serialized_data; + for (size_t i = 0; i < counters.size(); ++i) { + // LOG(ERROR)<<"attestation is: "< lk(leader_list_mutex_); + leader_list_[r] = randNum + 1; + return leader_list_[r]; +} + + +void Fides::AsyncSend() { + + uint64_t last_time = 0; + while (!IsStop()) { + auto txn = txns_.Pop(); + if (txn == nullptr) { + if(start_){ + //LOG(ERROR)<<"not enough txn"; + //break; + } + continue; + } + + queue_size_--; + //LOG(ERROR)<<"txn before waiting time:"<create_time()<<" queue size:"<CurrentRound(); + std::unique_lock lk(mutex_); + vote_cv_.wait_for(lk, std::chrono::microseconds(10000), + [&] { return proposal_manager_->Ready(); }); + + if (proposal_manager_->Ready()) { + start_ = 1; + break; + } + } + + //LOG(ERROR)<<"txn waiting time:"<create_time(); + txn->set_queuing_time(GetCurrentTime() - txn->create_time()); + global_stats_->AddQueuingLatency(GetCurrentTime() - txn->create_time()); + global_stats_->AddRoundLatency(GetCurrentTime() - last_time); + last_time = GetCurrentTime(); + + std::vector> txns; + txns.push_back(std::move(txn)); + for (int i = 1; i < batch_size_; ++i) { + auto txn = txns_.Pop(10); + if (txn == nullptr) { + continue; + break; + } + txn->set_queuing_time(GetCurrentTime() - txn->create_time()); + queue_size_--; + global_stats_->AddQueuingLatency(GetCurrentTime() - txn->create_time()); + txns.push_back(std::move(txn)); + } + + global_stats_->ConsumeTransactions(queue_size_); + + auto proposal = proposal_manager_->GenerateProposal(txns); + Broadcast(MessageType::NewBlock, *proposal); + + std::string block_data; + proposal->SerializeToString(&block_data); + global_stats_->AddBlockSize(block_data.size()); + + proposal_manager_->AddLocalBlock(std::move(proposal)); + + int round = proposal_manager_->CurrentRound() - 1; + //LOG(ERROR) << "bc txn:" << txns.size() << " round:" << round; + // TODO: change commit rule here + if (round > 1) { + if (round % 3 == 0) { + CommitRound(round - 3); + } + } + } +} + +// TODO: Change commit rule here. +void Fides::AsyncCommit() { + int previous_round = -3; + while (!IsStop()) { + std::unique_ptr round_or = commit_queue_.Pop(); + if (round_or == nullptr) { + continue; + } + + int round = *round_or; + //int64_t start_time = GetCurrentTime(); + //LOG(ERROR) << "commit round:" << round; + + int new_round = previous_round; + for (int r = previous_round + 3; r <= round; r += 3) { + int leader = GetLeader(r); + const Proposal * req = nullptr; + while(!IsStop()){ + req = proposal_manager_->GetRequest(r, leader); + // req:"<<(req==nullptr); + if (req == nullptr) { + //LOG(ERROR)<<" get leader:"< lk(mutex_); + vote_cv_.wait_for(lk, std::chrono::microseconds(100), + [&] { return true; }); + + continue; + } + break; + } + //LOG(ERROR)<<" get leader:"<header().create_time()); + int reference_num = proposal_manager_->GetReferenceNum(*req); + //LOG(ERROR)<<" get leader:"<GetRequest(j, pre_leader); + if (req == nullptr) { + continue; + } + int reference_num = proposal_manager_->GetReferenceNum(*req); + if (reference_num < limit_count_) { + continue; + } + + CommitProposal(j, pre_leader); + } + } + new_round = r; + } + //int64_t end_time = GetCurrentTime(); + //global_stats_->AddCommitRuntime(end_time-commit_time); + previous_round = new_round; + } +} + +// TODO: 3.Decrypt the request before executing. +void Fides::AsyncExecute() { +int64_t last_commit_time = 0; +int last_round = 0; + while (!IsStop()) { + std::unique_ptr proposal = execute_queue_.Pop(); + if (proposal == nullptr) { + continue; + } + + int commit_round = proposal->header().round(); + int64_t commit_time = GetCurrentTime(); + int64_t waiting_time = commit_time - proposal->queuing_time(); + global_stats_->AddCommitQueuingLatency(waiting_time); + //global_stats_->AddCommitInterval(commit_time - last_commit_time); + //last_commit_time = commit_time; + std::map>> ps; + std::queue> q; + q.push(std::move(proposal)); + + //LOG(ERROR)<<" commit round:"<SetCommittedRound(commit_round); + + //global_stats_->AddCommitRoundLatency(commit_round - last_round); + //last_round = commit_round; + + //int64_t start_time = GetCurrentTime(); + while (!q.empty()) { + std::unique_ptr p = std::move(q.front()); + q.pop(); + + for (auto& link : p->header().strong_cert().cert()) { + int link_round = link.round(); + int link_proposer = link.proposer(); + auto next_p = + proposal_manager_->FetchRequest(link_round, link_proposer); + if (next_p == nullptr) { + continue; + } + q.push(std::move(next_p)); + } + + + for (auto& link : p->header().weak_cert().cert()) { + int link_round = link.round(); + int link_proposer = link.proposer(); + auto next_p = + proposal_manager_->FetchRequest(link_round, link_proposer); + if (next_p == nullptr) { + // LOG(ERROR)<<"no data round:"<header().round()].push_back(std::move(p)); + } + + int num = 0; + int pro = 0; + for (auto& it : ps) { + for (auto& p : it.second) { + //LOG(ERROR) << "=============== commit proposal round :" + // << p->header().round() + // << " header round:"<header().round() + // << " commit round:"<header().proposer_id() + // << " transaction size:" << p->transactions_size() + // << " commit time:" + // << (GetCurrentTime() - p->header().create_time()) + // << " create time:" << p->header().create_time() + // <<" execute id:"<header().round()); + + global_stats_->AddCommitLatency(commit_time - p->header().create_time() - waiting_time); + global_stats_->AddCommitRoundLatency(commit_round - p->header().round()); + + // BatchUserRequest batch_request; // For transaction decryption + for (auto& tx : *p->mutable_transactions()) { + tx.set_id(execute_id_++); + num++; + // LOG(ERROR)<<" commit txn create time 1:"<(const_cast(encrypted_data.c_str())); + + // result = decrypt(enclave_, &ret, request_data, &decrypted_data, encrypted_data.size(), &decrypted_len); + // if (result != OE_OK || ret != 0) { + // LOG(ERROR) << "Host: decrypt failed with " << ret; + // LOG(ERROR) << "encrypted_data: "<mutable_request()->set_encrypted_data(""); + batch_request.mutable_user_requests(i)->mutable_request()->set_data(decrypted_request); + // user_request->set_data(decrypted_request); + // usleep(2000); + } + std::string new_tx_data; + batch_request.SerializeToString(&new_tx_data); + tx.set_data(new_tx_data); + */ + Commit(tx); + } + pro++; + } + } + int64_t end_time = GetCurrentTime(); + global_stats_->AddCommitRuntime(end_time-commit_time); + global_stats_->AddCommitTxn(num); + global_stats_->AddCommitBlock(pro); + } +} + +void Fides::CommitProposal(int round, int proposer) { + //LOG(ERROR) << "commit round:" << round << " proposer:" << proposer; + + //int64_t last_commit_time = 0; + int64_t commit_time = GetCurrentTime(); + global_stats_->AddCommitInterval(commit_time - last_commit_time_); + last_commit_time_ = commit_time; + + std::unique_ptr p = + proposal_manager_->FetchRequest(round, proposer); + if(p==nullptr){ + LOG(ERROR)<<"commit round:"<set_queuing_time(GetCurrentTime()); + execute_queue_.Push(std::move(p)); +} + +void Fides::CommitRound(int round) { + //LOG(ERROR)<<" commit round:"<(round)); +} + +bool Fides::ReceiveTransaction(std::unique_ptr txn) { + // LOG(ERROR)<<"recv txn"; + txn->set_create_time(GetCurrentTime()); + txns_.Push(std::move(txn)); + queue_size_++; + if (start_ == 0) { + std::unique_lock lk(mutex_); + vote_cv_.notify_all(); + } + return true; +} + +bool Fides::ReceiveBlock(std::unique_ptr proposal) { + //LOG(ERROR) << "recv block from " << proposal->header().proposer_id() + // << " round:" << proposal->header().round(); + int proposer_id = proposal->header().proposer_id(); + int round = proposal->header().round(); + std::string hash = proposal->hash(); + + { + std::unique_lock lk(check_block_mutex_); + proposal->set_queuing_time(GetCurrentTime()); + //std::unique_lock lk(check_block_mutex_); + // Verify the block, including the counter + if(!CheckBlock(*proposal)){ + std::unique_lock lk(future_block_mutex_); + //LOG(ERROR)<<"add future block:"<header().round()<<" sender:"<header().proposer_id(); + future_block_[proposal->header().round()][proposal->hash()] = std::move(proposal); + return false; + } + } + + // Generate certificate + auto cert = std::make_unique(); + global_stats_->AddExecutePrepareDelay(GetCurrentTime() - proposal->header().create_time()); + //global_stats_->AddCommitLatency(GetCurrentTime() - proposal->header().create_time()); + + cert->set_hash(hash); + cert->set_round(round); + cert->set_proposer(proposer_id); + *cert->mutable_strong_cert() = proposal->header().strong_cert(); + *cert->mutable_counter() = proposal->header().counter(); + + { + std::unique_lock lk(txn_mutex_); + //LOG(ERROR) << "recv block from " << proposal->header().proposer_id() + // << " round:" << proposal->header().round(); + global_stats_->AddCommitWaitingLatency(GetCurrentTime() - proposal->queuing_time()); + proposal->set_queuing_time(0); + { + // std::unique_lock lk(check_block_mutex_); + proposal_manager_->AddBlock(std::move(proposal)); + // CheckFutureCert(round, hash, proposer); + } + } + + ReceiveBlockCert(std::move(cert)); + + return true; + // return SendBlockAck(std::move(proposal)); +} + +/* +void Fides::ReceiveBlockACK(std::unique_ptr metadata) { + std::string hash = metadata->hash(); + int round = metadata->round(); + int sender = metadata->sender(); + std::unique_lock lk(txn_mutex_); + received_num_[hash][sender] = std::move(metadata); + //LOG(ERROR) << "recv block ack from:" << sender << " num:" << received_num_[hash].size()<<" round:"<GetLocalBlock(hash); + assert(p != nullptr); + assert(p->header().proposer_id() == id_); + assert(p->header().round() == round); + global_stats_->AddExecutePrepareDelay(GetCurrentTime() - p->header().create_time()); + //global_stats_->AddCommitLatency(GetCurrentTime() - p->header().create_time()); + *cert.mutable_strong_cert() = p->header().strong_cert(); + //LOG(ERROR)<<"send cert, round:"<header().round(); + Broadcast(MessageType::Cert, cert); + } +} +*/ + +bool Fides::VerifyCert(const Certificate & cert){ + // std::map hash_num; + // bool vote_num = false; + // for(auto& metadata: cert.metadata()){ + // bool valid = verifier_->VerifyMessage(metadata.hash(), metadata.sign()); + // if(!valid){ + // return false; + // } + // hash_num[metadata.hash()]++; + // if(hash_num[metadata.hash()]>=2*f_+1){ + // vote_num = true; + // } + // } + // return vote_num; + // TODO: change quorum here + return true; +} + +bool Fides::VerifyCounter(int round, const CounterInfo& counter) { + std::string attestation = counter.attestation(); + int counter_value = counter.value(); + if (attestation != "Fake Attestation" || counter_value != round) { + return false; + } + return true; +} + +bool Fides::CheckBlock(const Proposal& p){ + for(auto& link : p.header().strong_cert().cert()){ + int link_round = link.round(); + int link_proposer = link.proposer(); + if(!proposal_manager_->CheckCert(link_round, link_proposer)){ + //LOG(ERROR)<<" check block round:"<CheckCert(link_round, link_proposer)){ + //LOG(ERROR)<<" check block round:"<CheckBlock(cert.hash())){ + //future_cert_[cert->round()][cert->hash()] = std::move(cert); + return false; + } + return true; +} + +/* +bool Fides::SendBlockAck(std::unique_ptr proposal) { + + Metadata metadata; + metadata.set_sender(id_); + metadata.set_hash(proposal->hash()); + metadata.set_round(proposal->header().round()); + metadata.set_proposer(proposal->header().proposer_id()); + + std::string data_str = proposal->hash(); + auto hash_signature_or = verifier_->SignMessage(data_str); + if (!hash_signature_or.ok()) { + LOG(ERROR) << "Sign message fail"; + return false; + } + *metadata.mutable_sign()=*hash_signature_or; + metadata.set_sender(id_); + + { + //std::unique_lock lk(txn_mutex_); + //LOG(ERROR) << "recv block from " << proposal->header().proposer_id() + // << " round:" << proposal->header().round(); + int round = proposal->header().round(); + std::string hash = proposal->hash(); + int proposer = proposal->header().proposer_id(); + global_stats_->AddCommitWaitingLatency(GetCurrentTime() - proposal->queuing_time()); + proposal->set_queuing_time(0); + { + //std::unique_lock lk(check_block_mutex_); + proposal_manager_->AddBlock(std::move(proposal)); + CheckFutureCert(round, hash, proposer); + } + } + // SendMessage(MessageType::BlockACK, metadata, metadata.proposer()); + return true; +} +*/ + + +void Fides::CheckFutureBlock(int round){ + //LOG(ERROR)<<" check block round from cert:"<> hashs; + { + std::unique_lock clk(check_block_mutex_); + + { + std::unique_lock lk(future_block_mutex_); + if(future_block_.find(round) == future_block_.end()){ + return; + } + for(auto& it : future_block_[round]){ + if(CheckBlock(*it.second)){ + LOG(ERROR)<<" add new block round:"<header().round(); + hashs[it.first] = std::move(it.second); + } + } + + LOG(ERROR)<<" check round:"<header().round()<<" proposer:"<header().proposer_id(); + int block_round = it.second->header().round(); + //std::unique_lock lk(check_block_mutex_); + { + std::unique_lock lk(future_cert_mutex_); + if(future_cert_[block_round].find(it.first) != future_cert_[block_round].end()){ + std::unique_ptr cert = std::move(future_cert_[block_round][it.first]); + //LOG(ERROR)<<" add new cert:"<proposer(); + cert_queue_.Push(std::move(cert)); + future_cert_[block_round].erase(future_cert_[block_round].find(it.first)); + if(future_cert_[block_round].size() == 0){ + future_cert_.erase(future_cert_.find(block_round)); + } + } + } + + //LOG(ERROR)<<" Redo the proposal again, round: "< lk(future_cert_mutex_); + auto it = future_cert_[round].find(hash); + if(it != future_cert_[round].end()){ + //LOG(ERROR)<<" add back future cert, round:"<second)); + future_cert_[round].erase(it); + if(future_cert_[round].empty()){ + future_cert_.erase(future_cert_.find(round)); + } + } +} +*/ + +void Fides::AsyncProcessCert(){ + while(!IsStop()){ + std::unique_ptr cert = cert_queue_.Pop(); + if(cert == nullptr){ + continue; + } + + int round = cert->round(); + int cert_sender = cert->proposer(); + + //LOG(ERROR)<<" add cert, round:"< clk(check_block_mutex_); + proposal_manager_->AddCert(std::move(cert)); + { + std::unique_lock lk(mutex_); + vote_cv_.notify_all(); + } + } + CheckFutureBlock(round+1); + } +} + +void Fides::ReceiveBlockCert(std::unique_ptr cert) { + int64_t start_time = GetCurrentTime(); + if(!VerifyCert(*cert)){ + assert(1==0); + return; + } + + { + std::unique_lock lk(check_block_mutex_); + if(!CheckCert(*cert)){ + //LOG(ERROR)<<" add future cert round:"<round()<<" proposer:"<proposer(); + std::unique_lock lk(future_cert_mutex_); + future_cert_[cert->round()][cert->hash()] = std::move(cert); + return; + } + } + + int64_t end_time = GetCurrentTime(); + global_stats_->AddVerifyLatency(end_time-start_time); + + cert_queue_.Push(std::move(cert)); +} + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/algorithm/fides.h b/platform/consensus/ordering/fides/algorithm/fides.h new file mode 100644 index 000000000..c9265084e --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/fides.h @@ -0,0 +1,95 @@ +#pragma once + +#include +#include +#include +#include + +#include "common/crypto/signature_verifier.h" +#include "platform/statistic/stats.h" +#include "platform/common/queue/lock_free_queue.h" +#include "platform/consensus/ordering/common/algorithm/protocol_base.h" +#include "platform/consensus/ordering/fides/proto/proposal.pb.h" +#include "platform/consensus/ordering/fides/algorithm/proposal_manager.h" +#include "enclave/sgx_cpp_u.h" +#include "platform/config/resdb_config.h" +/* // For transaction decryption +// #include "platform/proto/resdb.pb.h" +// #include "platform/proto/resdb.pb.h" +// #include "proto/kv/kv.pb.h" +*/ + +namespace resdb { +namespace fides { + +class Fides : public common::ProtocolBase { + public: + Fides(int id, int f, int total_num, SignatureVerifier* verifier, + const ResDBConfig& config, oe_enclave_t* enclave); + ~Fides(); + + bool ReceiveTransaction(std::unique_ptr txn); + bool ReceiveBlock(std::unique_ptr proposal); + void ReceiveBlockACK(std::unique_ptr metadata); + void ReceiveBlockCert(std::unique_ptr cert); + + + private: + void CommitProposal(int round, int proposer); + void CommitRound(int round); + void AsyncCommit(); + void AsyncSend(); + void AsyncExecute(); + + bool VerifyCert(const Certificate& cert); + bool VerifyCounter(int round, const CounterInfo& counter); + + bool CheckBlock(const Proposal& p); + void CheckFutureBlock(int round); + void CheckFutureCert(const Proposal& proposal); + void CheckFutureCert(int round, const std::string& hash, int proposer); + bool CheckCert(const Certificate& cert); + bool SendBlockAck(std::unique_ptr proposal); + + void AsyncProcessCert(); + + int GetLeader(int r); + + private: + LockFreeQueue execute_queue_, pending_block_; + LockFreeQueue commit_queue_; + LockFreeQueue txns_; + + std::unique_ptr proposal_manager_; + SignatureVerifier* verifier_; + oe_enclave_t* enclave_; + oe_result_t result; + + std::thread send_thread_; + std::thread commit_thread_, execute_thread_; + std::mutex txn_mutex_, mutex_; + int limit_count_; + std::map>> received_num_; + std::condition_variable vote_cv_; + int start_ = 0; + int batch_size_ = 0; + int execute_id_ = 1; + std::atomic queue_size_; + + Stats* global_stats_; + + std::thread cert_thread_; + LockFreeQueue cert_queue_; + std::mutex future_block_mutex_, future_cert_mutex_, check_block_mutex_; + std::map>> future_block_; + std::map>> future_cert_; + int64_t last_commit_time_ = 0; + + std::mutex leader_list_mutex_; + std::unordered_map leader_list_; + + ResDBConfig config_; +}; + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/algorithm/proposal_manager.cpp b/platform/consensus/ordering/fides/algorithm/proposal_manager.cpp new file mode 100644 index 000000000..59963305f --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/proposal_manager.cpp @@ -0,0 +1,310 @@ +#include "platform/consensus/ordering/fides/algorithm/proposal_manager.h" + +#include + +#include "common/crypto/signature_verifier.h" +#include "common/utils/utils.h" + +namespace resdb { +namespace fides { + +ProposalManager::ProposalManager(int32_t id, int limit_count, oe_enclave_t* enclave) + : id_(id), limit_count_(limit_count), enclave_(enclave) { + round_ = 0; +} + +bool ProposalManager::VerifyHash(const Proposal &proposal){ + std::string data; + for (const auto& txn : proposal.transactions()) { + std::string tmp; + txn.SerializeToString(&tmp); + data += tmp; + } + + std::string header_data; + proposal.header().SerializeToString(&header_data); + data += header_data; + + std::string hash = SignatureVerifier::CalculateHash(data); + return hash == proposal.hash(); +} + +// TODO: 4.Add counter value into block +std::unique_ptr ProposalManager::GenerateProposal( + const std::vector>& txns) { + std::unique_ptr proposal = std::make_unique(); + std::string data; + { + std::unique_lock lk(txn_mutex_); + for (const auto& txn : txns) { + *proposal->add_transactions() = *txn; + std::string tmp; + txn->SerializeToString(&tmp); + data += tmp; + } + proposal->mutable_header()->set_proposer_id(id_); + proposal->mutable_header()->set_create_time(GetCurrentTime()); + proposal->mutable_header()->set_round(round_); + proposal->set_sender(id_); + + GetMetaData(proposal.get()); + } + + std::string header_data; + proposal->header().SerializeToString(&header_data); + data += header_data; + + std::string hash = SignatureVerifier::CalculateHash(data); + proposal->set_hash(hash); + + // Send counter value and attestation to enclave + size_t previous_cert_size = proposal->header().strong_cert().cert().size(); + unsigned char** attestation_array = new unsigned char*[previous_cert_size]; + size_t* attestation_size_array = new size_t[previous_cert_size]; + uint32_t* counter_value_array = new uint32_t[previous_cert_size]; + int i = 0; + + std::string serialized_data; + for(auto& link : proposal->header().strong_cert().cert()) { + // LOG(ERROR)<<"attestation is: "< counter = std::make_unique(); + CounterInfo* counter = proposal->mutable_header()->mutable_counter(); + counter->set_value(counter_value); + counter->set_attestation("Fake Attestation"); + // proposal->set_allocated_counter(counter.get()); + + // LOG(ERROR)<<"round: "< proposal) { + std::unique_lock lk(local_mutex_); + local_block_[proposal->hash()] = std::move(proposal); +} + +const Proposal* ProposalManager::GetLocalBlock(const std::string& hash) { + std::unique_lock lk(local_mutex_); + auto bit = local_block_.find(hash); + if (bit == local_block_.end()) { + LOG(ERROR) << " block not exist:" << hash.size(); + return nullptr; + } + return bit->second.get(); +} + +std::unique_ptr ProposalManager::FetchLocalBlock( + const std::string& hash) { + std::unique_lock lk(local_mutex_); + auto bit = local_block_.find(hash); + if (bit == local_block_.end()) { + //LOG(ERROR) << " block not exist:" << hash.size(); + return nullptr; + } + auto tmp = std::move(bit->second); + local_block_.erase(bit); + return tmp; +} + +void ProposalManager::AddBlock(std::unique_ptr proposal) { + std::unique_lock lk(txn_mutex_); + //LOG(ERROR) << "add block hash :" << proposal->hash().size() + // << " round:" << proposal->header().round() + // << " proposer:" << proposal->header().proposer_id(); + block_[proposal->hash()] = std::move(proposal); +} + +bool ProposalManager::CheckBlock(const std::string& hash){ + std::unique_lock lk(txn_mutex_); + //LOG(ERROR)<<"add block hash :"<hash().size()<<" round:"<header().round()<<" proposer:"<header().proposer_id(); + return block_.find(hash) != block_.end(); +} + +int ProposalManager::GetReferenceNum(const Proposal& req) { + int round = req.header().round(); + int round_1 = round + 1; + int proposer = req.header().proposer_id(); + std::unique_lock lk(txn_mutex_); + // return reference_[std::make_pair(round, proposer)]; + std::unordered_set reference_proposer; + for (auto& proposer_1 : reference_[std::make_pair(round, proposer)]) { + for (auto& proposer_2 : reference_[std::make_pair(round_1, proposer_1)]) { + if (reference_proposer.count(proposer_2) == 0) + reference_proposer.insert(proposer_2); + } + } + return reference_proposer.size(); +} + +std::unique_ptr ProposalManager::FetchRequest(int round, int sender) { + std::unique_lock lk(txn_mutex_); + auto it = cert_list_[round].find(sender); + if (it == cert_list_[round].end()) { + // LOG(ERROR)<<" cert from sender:"<second->hash(); + auto bit = block_.find(hash); + if (bit == block_.end()) { + LOG(ERROR) << " block from sender:" << sender << " round:" << round + << " not exist"; + assert(1 == 0); + return nullptr; + } + auto tmp = std::move(bit->second); + //cert_list_[round].erase(sender); + //LOG(ERROR)<<" featch sender done, round:"< lk(txn_mutex_); + auto it = cert_list_[round].find(sender); + if (it == cert_list_[round].end()) { + //LOG(ERROR) << " cert from sender:" << sender << " round:" << round + // << " not exist"; + return nullptr; + } + std::string hash = it->second->hash(); + auto bit = block_.find(hash); + if (bit == block_.end()) { + LOG(ERROR) << " block from sender:" << sender << " round:" << round + << " not exist"; + return nullptr; + } + return bit->second.get(); +} + + +// TODO: 6.Remove Cert Check counter value instead. +void ProposalManager::AddCert(std::unique_ptr cert) { + int proposer = cert->proposer(); + int round = cert->round(); + std::string hash = cert->hash(); + + // LOG(ERROR)<<"add cert sender:"< lk(txn_mutex_); + + // LOG(ERROR)<<"strong cert size:"<strong_cert().cert_size(); + std::unordered_set refered_proposer; + for (auto& link : cert->strong_cert().cert()) { + int link_round = link.round(); + int link_proposer = link.proposer(); + reference_[std::make_pair(link_round, link_proposer)].push_back(cert->proposer()); + } + + cert->mutable_strong_cert()->Clear(); + cert->mutable_metadata()->Clear(); + + auto tmp = std::make_unique(*cert); + cert_list_[round][proposer] = std::move(cert); + latest_cert_from_sender_[proposer] = std::move(tmp); +} + +bool ProposalManager::CheckCert(int round, int sender) { + std::unique_lock lk(txn_mutex_); + return cert_list_[round].find(sender) != cert_list_[round].end(); +} + +void ProposalManager::GetMetaData(Proposal* proposal) { + if (round_ == 0) { + return; + } + assert(cert_list_[round_ - 1].size() >= limit_count_); + assert(cert_list_[round_ - 1].find(id_) != cert_list_[round_ - 1].end()); + + //LOG(ERROR)<<"get metadata:"<<" proposal round:"<header().round(); + std::set meta_ids; + for (const auto& preview_cert : cert_list_[round_ - 1]) { + *proposal->mutable_header()->mutable_strong_cert()->add_cert() = + *preview_cert.second; + meta_ids.insert(preview_cert.first); + //LOG(ERROR)<<"add strong round:"<round() + //<<" id:"<proposer()<<" first:"<header().round(); + if (meta_ids.size() == limit_count_) { + break; + } + } + //LOG(ERROR)<<"strong link:"<header().strong_cert().cert_size()<<" round:"<header().round(); + + for (const auto& meta : latest_cert_from_sender_) { + // LOG(ERROR)<<"check:"<header().round(); + if (meta_ids.find(meta.first) != meta_ids.end()) { + continue; + } + if (meta.second->round() >= round_) { + for (int j = round_-1; j >= 0; --j) { + if (cert_list_[j].find(meta.first) != cert_list_[j].end()) { + //LOG(ERROR) << " add weak cert from his:" << j + // << " proposer:" << meta.first<< " cert round:"<round()<< " proposal round:"<header().round(); + *proposal->mutable_header()->mutable_weak_cert()->add_cert() = + *cert_list_[j][meta.first]; + break; + } + } + } else { + assert(meta.second->round() <= round_); + //LOG(ERROR)<<"add weak cert:"<round()<<" proposer:"<proposer()<<" proposal round:"<header().round(); + *proposal->mutable_header()->mutable_weak_cert()->add_cert() = + *meta.second; + } + } + //LOG(ERROR)<<"weak link:"<header().weak_cert().cert_size()<<" round:"<header().round(); +} + +bool ProposalManager::Ready() { + std::unique_lock lk(txn_mutex_); + if (round_ == 0) { + return true; + } + if (cert_list_[round_ - 1].size() < limit_count_) { + return false; + } + if (cert_list_[round_ - 1].find(id_) == cert_list_[round_ - 1].end()) { + return false; + } + return true; +} + +std::vector ProposalManager::GetCounterFromRound(int round) { + std::vector counters; + for (const auto& preview_cert : cert_list_[round]) { + counters.push_back(preview_cert.second->counter()); + if (counters.size() == limit_count_) { + break; + } + } + return counters; +} + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/algorithm/proposal_manager.h b/platform/consensus/ordering/fides/algorithm/proposal_manager.h new file mode 100644 index 000000000..2d61661ec --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/proposal_manager.h @@ -0,0 +1,60 @@ +#pragma once + +#include "platform/consensus/ordering/fides/proto/proposal.pb.h" +#include "enclave/sgx_cpp_u.h" + +namespace resdb { +namespace fides { + +class ProposalManager { + public: + ProposalManager(int32_t id, int limit_count, oe_enclave_t* enclave); + + std::unique_ptr GenerateProposal( + const std::vector>& txns); + + int CurrentRound(); + void AddLocalBlock(std::unique_ptr proposal); + const Proposal* GetLocalBlock(const std::string& hash); + std::unique_ptr FetchLocalBlock(const std::string& hash); + + void AddBlock(std::unique_ptr proposal); + void AddCert(std::unique_ptr cert); + bool Ready(); + + const Proposal* GetRequest(int round, int sender); + std::unique_ptr FetchRequest(int round, int sender); + int GetReferenceNum(const Proposal& req); + + bool VerifyHash(const Proposal &proposal); + + bool CheckCert(int round, int sender); + bool CheckBlock(const std::string& hash); + + void SetCommittedRound(int r); + + std::vector GetCounterFromRound(int round); + + protected: + void GetMetaData(Proposal* proposal); + + private: + int32_t id_; + int round_; + int limit_count_; + std::map> block_, local_block_; + + std::map>> cert_list_; + std::map> latest_cert_from_sender_; + + std::mutex txn_mutex_, local_mutex_; + std::map, std::vector> reference_; + + std::atomic committed_round_ = 0; + + oe_enclave_t* enclave_; + oe_result_t result; +}; + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/algorithm/transaction_collector.cpp b/platform/consensus/ordering/fides/algorithm/transaction_collector.cpp new file mode 100644 index 000000000..19c000377 --- /dev/null +++ b/platform/consensus/ordering/fides/algorithm/transaction_collector.cpp @@ -0,0 +1,56 @@ +/* + * 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 "platform/consensus/ordering/rcc/protocol/transaction_collector.h" + +#include + +namespace resdb { + +TransactionStatue TransactionCollector::GetStatus() const { return status_; } + +// TODO: 2.Encrypt the request from the client +int TransactionCollector::AddRequest( + std::unique_ptr request, int sender_id, int type, + std::function* status)> + call_back) { + if (status_.load() == EXECUTED) { + return -2; + } + + if (request) { + request_ = std::move(request); + call_back(*request, 1, &status_); + } else { + senders_[type].insert(sender_id); + if (request_) { + call_back(*request_, senders_[type].size(), &status_); + } + } + return 0; +} + +} // namespace resdb diff --git a/chain/storage/res_leveldb.h b/platform/consensus/ordering/fides/algorithm/transaction_collector.h similarity index 55% rename from chain/storage/res_leveldb.h rename to platform/consensus/ordering/fides/algorithm/transaction_collector.h index f2c526319..b38a41f62 100644 --- a/chain/storage/res_leveldb.h +++ b/platform/consensus/ordering/fides/algorithm/transaction_collector.h @@ -25,41 +25,47 @@ #pragma once -#include -#include -#include +#include -#include "chain/storage/storage.h" -#include "leveldb/db.h" -#include "leveldb/write_batch.h" -#include "platform/proto/replica_info.pb.h" +#include "platform/statistic/stats.h" namespace resdb { -std::unique_ptr NewResLevelDB(const char* cert_file, - resdb::ResConfigData config_data); +enum TransactionStatue { + None = 0, + Prepare = -999, + READY_PREPARE = 1, + READY_COMMIT = 2, + READY_EXECUTE = 3, + EXECUTED = 4, +}; -class ResLevelDB : public Storage { +class TransactionCollector { public: - ResLevelDB(const char* cert_file, std::optional config_data); + TransactionCollector(int64_t seq) + : seq_(seq), status_(TransactionStatue::None) {} - virtual ~ResLevelDB(); - int SetValue(const std::string& key, const std::string& value) override; - std::string GetValue(const std::string& key) override; - std::string GetAllValues(void) override; - std::string GetRange(const std::string& min_key, - const std::string& max_key) override; + ~TransactionCollector() = default; - bool Flush() override; + int AddRequest( + std::unique_ptr request, int sender_id, + int type, + std::function* status)> + call_back); - private: - void CreateDB(const std::string& path); + TransactionStatue GetStatus() const; + int64_t GetSeq() { return seq_; } + + std::unique_ptr GetData() { + return std::move(request_); + } private: - std::unique_ptr db_ = nullptr; - ::leveldb::WriteBatch batch_; - unsigned int write_buffer_size_ = 64 << 20; - unsigned int write_batch_size_ = 1; + int64_t seq_; + std::unique_ptr request_; + std::atomic status_ = TransactionStatue::None; + std::set senders_[10]; }; } // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/common/BUILD b/platform/consensus/ordering/fides/executor/common/BUILD new file mode 100644 index 000000000..7fecf8700 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/common/BUILD @@ -0,0 +1,19 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "utils", + hdrs = ["utils.h"], + deps = [ + "//third_party:evm_lib", + ], +) + +cc_library( + name = "contract_execute_info", + hdrs = ["contract_execute_info.h"], + deps = [ + ":utils", + "//service/contract/proto:func_params_cc_proto", + ], +) + diff --git a/platform/consensus/ordering/fides/executor/common/contract_execute_info.h b/platform/consensus/ordering/fides/executor/common/contract_execute_info.h new file mode 100644 index 000000000..f7de8b48e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/common/contract_execute_info.h @@ -0,0 +1,71 @@ +/* + * 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 "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "eEVM/address.h" + +#include "service/contract/executor/common/utils.h" + +namespace resdb { +namespace contract { + +struct ContractExecuteInfo { + eevm::Address caller_address; + eevm::Address contract_address; + std::string func_addr; + Params func_params; + int64_t commit_id; + uint64_t user_id; + bool is_only; + ContractExecuteInfo(){} + ContractExecuteInfo( + eevm::Address caller_address, + eevm::Address contract_address, + std::string func_addr, + Params func_params, + int64_t commit_id): caller_address(caller_address), contract_address(contract_address), func_addr(func_addr), func_params(func_params), commit_id(commit_id), is_only(false){} +}; + +struct ExecuteResp { + int ret; + absl::Status state; + int64_t commit_id; + Address contract_address; + //ConcurrencyController :: ModifyMap rws; + std::string result; + int retry_time = 0; + uint64_t user_id = 0; + double runtime = 0; + double delay = 0; +}; + + +} // namespace contract +} // namespace resdb diff --git a/chain/storage/txn_memory_db.h b/platform/consensus/ordering/fides/executor/common/utils.h similarity index 76% rename from chain/storage/txn_memory_db.h rename to platform/consensus/ordering/fides/executor/common/utils.h index de69d70cb..2d243d66f 100644 --- a/chain/storage/txn_memory_db.h +++ b/platform/consensus/ordering/fides/executor/common/utils.h @@ -25,24 +25,12 @@ #pragma once -#include -#include - -#include "platform/proto/resdb.pb.h" +#include "eEVM/address.h" namespace resdb { +namespace contract { -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_; -}; +typedef eevm::Address Address; +} } // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/common/x_verifier.cpp b/platform/consensus/ordering/fides/executor/common/x_verifier.cpp new file mode 100644 index 000000000..b859690ca --- /dev/null +++ b/platform/consensus/ordering/fides/executor/common/x_verifier.cpp @@ -0,0 +1,188 @@ +/* + * 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 "service/contract/executor/manager/x_verifier.h" + +#include + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + + +XVerifier:: XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num):storage_(storage), gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time++; + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +XVerifier::~XVerifier(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + + +bool XVerifier::VerifyContract( + const std::vector& request_list, + const std::vector& rws_list) { + std::vector d; + std::map > g; + std::map id; + + for(int i = 0; i < rws_list.size();++i){ + d.push_back(0); + for(auto it : rws_list[i]){ + const Address& address = it.first; + //LOG(ERROR)<<"i ="< q; + for(int i = 0; i < request_list.size();++i){ + if(d[i]==0){ + auto context = std::make_unique(request_list[i]); + context->GetContractExecuteInfo()->commit_id = i; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add:"<0){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + process_num--; + int resp_commit_id = resp->commit_id; + bool ret = controller_->Commit(resp_commit_id); + if(!ret){ + return false; + } + //LOG(ERROR)<<"verify:"<GetChangeList(resp_commit_id); + if(!RWSEqual(*rws, rws_list[resp_commit_id])){ + LOG(ERROR)<<"rws not equal"; + return false; + } + for(int n_id : g[resp_commit_id]){ + //LOG(ERROR)<<"next id:"<(request_list[n_id]); + context->GetContractExecuteInfo()->commit_id = n_id; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add next:"<first != it2->first){ + LOG(ERROR)<<"address not equal:"<first<<" "<first; + return false; + } + if(it1->second.size() != it2->second.size()){ + LOG(ERROR)<<"item size not equal:"<second.size()<<" "<second.size(); + return false; + } + for(int i = 0; i < it1->second.size(); i++){ + if(it1->second[i] != it2->second[i]){ + LOG(ERROR)<<"data not equal"; + return false; + } + } + } + return true; +} + + +} // namespace contract +} // namespace resdb + diff --git a/platform/consensus/ordering/fides/executor/common/x_verifier.h b/platform/consensus/ordering/fides/executor/common/x_verifier.h new file mode 100644 index 000000000..d93482af2 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/common/x_verifier.h @@ -0,0 +1,72 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/v_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/contract_verifier.h" +#include "service/contract/executor/manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +class XVerifier : public ContractVerifier { + public: + XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~XVerifier(); + + bool VerifyContract( + const std::vector& request_list, + const std::vector& rws_list); + + bool RWSEqual(const ConcurrencyController :: ModifyMap&a, const ConcurrencyController :: ModifyMap&b); + + private: + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::unique_ptr controller_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + const int worker_num_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/eo_service/BUILD b/platform/consensus/ordering/fides/executor/eo_service/BUILD new file mode 100644 index 000000000..46776a78a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "contract_transaction_manager", + srcs = ["contract_transaction_manager.cpp"], + hdrs = ["contract_transaction_manager.h"], + deps = [ + "//platform/config:resdb_config_utils", + "//service/contract/executor/manager:address_manager", + "//service/contract/executor/manager:contract_manager", + "//service/contract/proto:rpc_cc_proto", + "//service/utils:server_factory", + ], +) + +cc_test( + name = "contract_transaction_manager_test", + srcs = ["contract_transaction_manager_test.cpp"], + data = [ + "//service/contract/executor/service/test_data:contract.json", + ], + deps = [ + ":contract_transaction_manager", + "//common/test:test_main", + ], +) diff --git a/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.cpp b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.cpp new file mode 100644 index 000000000..9010b69f3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.cpp @@ -0,0 +1,127 @@ +/* + * 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 "service/contract/executor/service/contract_transaction_manager.h" + +#include + +namespace resdb { +namespace contract { + +ContractTransactionManager::ContractTransactionManager(void) + : contract_manager_(std::make_unique(std::make_unique())), + address_manager_(std::make_unique()) {} + +std::unique_ptr ContractTransactionManager::ExecuteData( + const std::string& client_request) { + Request request; + Response response; + + if (!request.ParseFromString(client_request)) { + LOG(ERROR) << "parse data fail"; + return nullptr; + } + + int ret = 0; + if (request.cmd() == Request::CREATE_ACCOUNT) { + absl::StatusOr account_or = CreateAccount(); + if (account_or.ok()) { + response.mutable_account()->Swap(&(*account_or)); + } else { + ret = -1; + } + } else if (request.cmd() == Request::DEPLOY) { + absl::StatusOr contract_or = Deploy(request); + if (contract_or.ok()) { + response.mutable_contract()->Swap(&(*contract_or)); + } else { + ret = -1; + } + } else if (request.cmd() == Request::EXECUTE) { + auto res_or = Execute(request); + if (res_or.ok()) { + response.set_res(*res_or); + } else { + ret = -1; + } + } + + response.set_ret(ret); + + std::unique_ptr resp_str = std::make_unique(); + if (!response.SerializeToString(resp_str.get())) { + return nullptr; + } + + return resp_str; +} + +absl::StatusOr ContractTransactionManager::CreateAccount() { + std::string address = + AddressManager::AddressToHex(address_manager_->CreateRandomAddress()); + Account account; + account.set_address(address); + return account; +} + +absl::StatusOr ContractTransactionManager::Deploy( + const Request& request) { + Address caller_address = + AddressManager::HexToAddress(request.caller_address()); + if (!address_manager_->Exist(caller_address)) { + LOG(ERROR) << "caller doesn't have an account"; + return absl::InvalidArgumentError("Account not exist."); + } + + Address contract_address = + contract_manager_->DeployContract(caller_address, request.deploy_info()); + + if (contract_address > 0) { + Contract contract; + contract.set_owner_address(request.caller_address()); + contract.set_contract_address( + AddressManager::AddressToHex(contract_address)); + contract.set_contract_name(request.deploy_info().contract_name()); + return contract; + } + return absl::InternalError("Deploy Contract fail."); +} + +absl::StatusOr ContractTransactionManager::Execute( + const Request& request) { + Address caller_address = + AddressManager::HexToAddress(request.caller_address()); + if (!address_manager_->Exist(caller_address)) { + LOG(ERROR) << "caller doesn't have an account"; + return absl::InvalidArgumentError("Account not exist."); + } + + return contract_manager_->ExecContract( + caller_address, AddressManager::HexToAddress(request.contract_address()), + request.func_params()); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.h b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.h new file mode 100644 index 000000000..33638c2f4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager.h @@ -0,0 +1,56 @@ +/* + * 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 "platform/config/resdb_config_utils.h" +#include "platform/consensus/execution/transaction_manager.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/contract_manager.h" +#include "service/contract/proto/func_params.pb.h" +#include "service/contract/proto/rpc.pb.h" + +namespace resdb { +namespace contract { + +class ContractTransactionManager : public TransactionManager { + public: + ContractTransactionManager(void); + virtual ~ContractTransactionManager() = default; + + std::unique_ptr ExecuteData(const std::string& request) override; + + private: + absl::StatusOr CreateAccount(); + absl::StatusOr Deploy(const Request& request); + absl::StatusOr Execute(const Request& request); + + private: + std::unique_ptr contract_manager_; + std::unique_ptr address_manager_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager_test.cpp b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager_test.cpp new file mode 100644 index 000000000..e0d59e186 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/contract_transaction_manager_test.cpp @@ -0,0 +1,270 @@ +/* + * 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 "service/contract/executor/service/contract_transaction_manager.h" + +#include +#include + +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/service/"; + +std::string ToString(const Request& request) { + std::string ret; + request.SerializeToString(&ret); + return ret; +} + +class ContractTransactionManagerTest : public Test { + public: + ContractTransactionManagerTest() { + std::string contract_path = test_dir + "test_data/contract.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + nlohmann::json definition = nlohmann::json::parse(contract_fstream); + contracts_json_ = definition["contracts"]; + } + + Account CreateAccount() { + Request request; + Response response; + + request.set_cmd(Request::CREATE_ACCOUNT); + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + response.ParseFromString(*ret); + return response.account(); + } + + absl::StatusOr Deploy(const Account& account, + DeployInfo deploy_info) { + Request request; + Response response; + + request.set_caller_address(account.address()); + *request.mutable_deploy_info() = deploy_info; + request.set_cmd(Request::DEPLOY); + + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + + response.ParseFromString(*ret); + if (response.ret() == 0) { + return response.contract(); + } else { + return absl::InternalError("DeployFail."); + } + } + + absl::StatusOr Execute(const std::string& caller_address, + const std::string& contract_address, + const Params& params) { + Request request; + Response response; + + request.set_caller_address(caller_address); + request.set_contract_address(contract_address); + request.set_cmd(Request::EXECUTE); + *request.mutable_func_params() = params; + + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + + response.ParseFromString(*ret); + + if (response.ret() == 0) { + return eevm::to_uint256(response.res()); + } else { + return absl::InternalError("DeployFail."); + } + } + + protected: + nlohmann::json contracts_json_; + ContractTransactionManager executor_; +}; + +TEST_F(ContractTransactionManagerTest, ExecContract) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_TRUE(contract_or.ok()); + Contract contract = *contract_or; + + // query owner should return 1000 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 0x3e8); + } + + Account transfer_receiver = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(transfer_receiver.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(transfer_receiver.address()); + func_params.add_param("400"); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 1); + } + + // query owner should return 600 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 600); + } + + // receiver 400 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(transfer_receiver.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 400); + } +} + +TEST_F(ContractTransactionManagerTest, DeployFail) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + // deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_FALSE(contract_or.ok()); +} + +TEST_F(ContractTransactionManagerTest, NoFunc) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_TRUE(contract_or.ok()); + Contract contract = *contract_or; + + { + Params func_params; + func_params.set_func_name("balanceOf()"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_FALSE(result.ok()); + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/eo_service/test_data/BUILD b/platform/consensus/ordering/fides/executor/eo_service/test_data/BUILD new file mode 100644 index 000000000..65e3dc3d9 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/test_data/BUILD @@ -0,0 +1 @@ +exports_files(["contract.json"]) diff --git a/platform/consensus/ordering/fides/executor/eo_service/test_data/contract.json b/platform/consensus/ordering/fides/executor/eo_service/test_data/contract.json new file mode 100644 index 000000000..e9c0d972f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/eo_service/test_data/contract.json @@ -0,0 +1,19 @@ +{ + "contracts": + { + "ERC20.sol:ERC20Token": + { + "bin": "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a72315820e4ae92c4517e475d9f77ac0bfcd10463a09239f34734fc0ab4d7966450fb428464736f6c63430005100032", + "hashes": + { + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/manager/BUILD b/platform/consensus/ordering/fides/executor/manager/BUILD new file mode 100644 index 000000000..2da517eb4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/BUILD @@ -0,0 +1,632 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "address_manager", + srcs = ["address_manager.cpp"], + hdrs = ["address_manager.h"], + deps = [ + "//service/contract/executor/common:utils", + "//common:comm", + ], +) + +cc_test( + name = "address_manager_test", + srcs = ["address_manager_test.cpp"], + deps = [ + ":address_manager", + "//common/test:test_main", + ], +) + +cc_library( + name = "data_storage", + srcs = ["data_storage.cpp"], + hdrs = ["data_storage.h"], + deps = [ + "//service/contract/executor/common:utils", + "//common:comm", + ], +) + +cc_library( + name = "leveldb_storage", + srcs = ["leveldb_storage.cpp"], + hdrs = ["leveldb_storage.h"], + deps = [ + ":data_storage", + "//storage:res_leveldb", + "//common:comm", + ], +) + + + +cc_library( + name = "mock_data_storage", + hdrs = ["mock_data_storage.h"], + deps = [ + ":data_storage", + "//common/test:test" + ], +) + +cc_library( + name = "mock_d_storage", + hdrs = ["mock_d_storage.h"], + deps = [ + ":d_storage", + "//common/test:test" + ], +) + + +cc_library( + name = "d_storage", + srcs = ["d_storage.cpp"], + hdrs = ["d_storage.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + +cc_library( + name = "leveldb_d_storage", + srcs = ["leveldb_d_storage.cpp"], + hdrs = ["leveldb_d_storage.h"], + deps = [ + ":data_storage", + "//storage:res_leveldb", + "//common:comm", + ], +) + + + +cc_library( + name = "leveldb", + srcs = ["leveldb.cpp"], + hdrs = ["leveldb.h"], + deps = [ + ":data_storage", + "//common:comm", + "//storage:res_leveldb" + ], +) + +cc_library( + name = "global_view", + srcs = ["global_view.cpp"], + hdrs = ["global_view.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + +cc_library( + name = "local_view", + srcs = ["local_view.cpp"], + hdrs = ["local_view.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + +cc_test( + name = "local_view_test", + srcs = ["local_view_test.cpp"], + deps = [ + ":local_view", + ":two_phase_controller", + "//common/test:test_main", + ], +) + +cc_library( + name = "evm_state", + hdrs = ["evm_state.h"], + deps = [ + "//service/contract/executor/common:utils", + "//common:comm", + ], +) + +cc_library( + name = "global_state", + srcs = ["global_state.cpp"], + hdrs = ["global_state.h"], + deps = [ + ":evm_state", + "//service/contract/executor/common:utils", + ":global_view", + "//common:comm", + ], +) + +cc_library( + name = "local_state", + srcs = ["local_state.cpp"], + hdrs = ["local_state.h"], + deps = [ + ":evm_state", + "//service/contract/executor/common:utils", + ":local_view", + "//common:comm", + ], +) + +cc_library( + name = "concurrency_controller", + srcs = ["concurrency_controller.cpp"], + hdrs = ["concurrency_controller.h"], + deps = [ + ":data_storage", + ], +) + +cc_library( + name = "two_phase_ooo_controller", + srcs = ["two_phase_ooo_controller.cpp"], + hdrs = ["two_phase_ooo_controller.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + +cc_library( + name = "two_phase_ooo_committer", + srcs = ["two_phase_ooo_committer.cpp"], + hdrs = ["two_phase_ooo_committer.h"], + deps = [ + ":contract_committer", + ":two_phase_ooo_controller", + ":contract_executor", + ":local_state", + ":global_state", + "//common:comm", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + + +cc_library( + name = "two_phase_controller", + srcs = ["two_phase_controller.cpp"], + hdrs = ["two_phase_controller.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + +cc_test( + name = "two_phase_controller_test", + srcs = ["two_phase_controller_test.cpp"], + deps = [ + ":two_phase_controller", + "//common/test:test_main", + ], +) + +cc_library( + name = "ooo_controller", + srcs = ["ooo_controller.cpp"], + hdrs = ["ooo_controller.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + +cc_library( + name = "sequential_cc_controller", + srcs = ["sequential_cc_controller.cpp"], + hdrs = ["sequential_cc_controller.h"], + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + +cc_test( + name = "sequential_cc_controller_test", + srcs = ["sequential_cc_controller_test.cpp"], + deps = [ + ":sequential_cc_controller", + "//common/test:test_main", + ], +) + +cc_library( + name = "streaming_dq_controller", + srcs = ["streaming_dq_controller.cpp"], + hdrs = ["streaming_dq_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + + +cc_library( + name = "test_controller", + srcs = ["test_controller.cpp"], + hdrs = ["test_controller.h"], + testonly = True, + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + + +cc_library( + name = "contract_executor", + srcs = ["contract_executor.cpp"], + hdrs = ["contract_executor.h"], + deps = [ + ":evm_state", + ":concurrency_controller", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + "//service/contract/executor/common:contract_execute_info", + ], +) + +cc_library( + name = "committer_context", + srcs = ["committer_context.cpp"], + hdrs = ["committer_context.h"], + deps = [ + ":contract_committer", + ], +) + +cc_library( + name = "contract_committer", + hdrs = ["contract_committer.h"], + deps = [ + ":contract_executor", + "//service/contract/proto:func_params_cc_proto", + "//service/contract/executor/common:contract_execute_info", + ], +) + +cc_library( + name = "contract_verifier", + hdrs = ["contract_verifier.h"], + srcs = ["contract_verifier.cpp"], + deps = [ + ":contract_executor", + ":contract_committer", + ":committer_context", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "ooo_committer", + srcs = ["ooo_committer.cpp"], + hdrs = ["ooo_committer.h"], + deps = [ + ":contract_committer", + ":ooo_controller", + ":contract_executor", + ":local_state", + ":global_state", + "//common:comm", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "two_phase_committer", + srcs = ["two_phase_committer.cpp"], + hdrs = ["two_phase_committer.h"], + deps = [ + ":contract_committer", + ":two_phase_controller", + ":contract_executor", + ":local_state", + ":global_state", + "//common:comm", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "two_phase_committer_test", + srcs = ["two_phase_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_deployer", + ":two_phase_committer", + ":address_manager", + "//common/test:test_main", + ], +) + + +cc_library( + name = "sequential_concurrency_committer", + srcs = ["sequential_concurrency_committer.cpp"], + hdrs = ["sequential_concurrency_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":sequential_cc_controller", + ":local_state", + ":global_state", + ":committer_context", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "x_committer", + srcs = ["x_committer.cpp"], + hdrs = ["x_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":x_controller", + ":streaming_dq_controller", + ":local_state", + ":global_state", + ":committer_context", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "x_controller", + srcs = ["x_controller.cpp"], + hdrs = ["x_controller.h"], + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + +cc_library( + name = "v_controller", + srcs = ["v_controller.cpp"], + hdrs = ["v_controller.h"], + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + + +cc_library( + name = "x_verifier", + srcs = ["x_verifier.cpp"], + hdrs = ["x_verifier.h"], + deps = [ + ":contract_verifier", + ":local_state", + ":global_state", + ":v_controller", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "x_committer_test", + srcs = ["x_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_deployer", + ":x_committer", + ":address_manager", + ":mock_data_storage", + "//common/test:test_main", + ], +) + +cc_test( + name = "x_verifier_test", + srcs = ["x_verifier_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":x_verifier", + ":x_committer", + ":address_manager", + ":mock_data_storage", + "//common/test:test_main", + ], +) + +cc_library( + name = "streaming_single_committer", + srcs = ["streaming_single_committer.cpp"], + hdrs = ["streaming_single_committer.h"], + deps = [ + ":committer_context", + ":contract_committer", + ":contract_executor", + ":global_state", + "//common/utils:utils", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "streaming_dq_committer", + srcs = ["streaming_dq_committer.cpp"], + hdrs = ["streaming_dq_committer.h"], + deps = [ + ":committer_context", + ":contract_committer", + ":contract_executor", + ":streaming_dq_controller", + ":local_state", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "streaming_dq_committer_test", + srcs = ["streaming_dq_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_d_storage", + ":streaming_dq_committer", + "//common/test:test_main", + ], +) + + +cc_test( + name = "sequential_concurrency_committer_test", + srcs = ["sequential_concurrency_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_data_storage", + ":sequential_concurrency_committer", + "//common/test:test_main", + ], +) + +cc_library( + name = "streaming_controller", + srcs = ["streaming_controller.cpp"], + hdrs = ["streaming_controller.h"], + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + +cc_library( + name = "streaming_committer", + srcs = ["streaming_committer.cpp"], + hdrs = ["streaming_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":streaming_controller", + ":local_state", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "test_committer", + hdrs = ["test_committer.h"], + srcs = ["test_committer.cpp"], + testonly = True, + deps = [ + ":test_controller", + ":contract_committer", + ":contract_executor", + ":global_state", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "contract_deployer", + srcs = ["contract_deployer.cpp"], + hdrs = ["contract_deployer.h"], + deps = [ + ":concurrency_controller", + ":contract_committer", + ":global_state", + ":address_manager", + ], +) + +cc_test( + name = "contract_deployer_test", + srcs = ["contract_deployer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":test_committer", + ":contract_deployer", + "//common/test:test_main", + ], +) + +cc_library( + name = "contract_manager", + srcs = ["contract_manager.cpp"], + hdrs = ["contract_manager.h"], + deps = [ + ":contract_deployer", + ":two_phase_committer", + ":sequential_concurrency_committer", + ":ooo_committer", + ":two_phase_ooo_committer", + ":x_committer", + ":x_verifier", + ":streaming_committer", + ":concurrency_controller", + ":streaming_single_committer", + ":streaming_dq_committer", + ":global_state", + ":address_manager", + "//service/contract/executor/common:utils", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "contract_manager_test", + srcs = ["contract_manager_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_manager", + "//common/test:test_main", + ], +) + diff --git a/platform/consensus/ordering/fides/executor/manager/address_manager.cpp b/platform/consensus/ordering/fides/executor/manager/address_manager.cpp new file mode 100644 index 000000000..f62fc53e8 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/address_manager.cpp @@ -0,0 +1,83 @@ +/* + * 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 "service/contract/executor/manager/address_manager.h" + +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +Address AddressManager::CreateRandomAddress() { + std::vector raw(20); + std::generate(raw.begin(), raw.end(), []() { return rand(); }); + Address address = eevm::from_big_endian(raw.data(), raw.size()); + users_.insert(address); + return address; +} + +bool AddressManager::CreateAddress(const Address& address) { + if(users_.find(address) != users_.end()){ + return false; + } + users_.insert(address); + return true; +} + + +bool AddressManager::Exist(const Address& address) { + return users_.find(address) != users_.end(); +} + +Address AddressManager::CreateContractAddress(const Address& owner) { + return eevm::generate_address(owner, 0u); +} + +std::string AddressManager::AddressToHex(const Address& address) { + return eevm::to_hex_string(address); +} + +Address AddressManager::HexToAddress(const std::string& address) { + return eevm::to_uint256(address); +} + +uint256_t AddressManager::AddressToSHAKey(const Address& address) { + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + //code[63]=1; + //LOG(ERROR)<<"code size:"<(code.size()), h); + //LOG(ERROR)<<"get h:"< + +#include "service/contract/executor/common/utils.h" + +namespace resdb { +namespace contract { + +class AddressManager { + public: + AddressManager() {} + + // Create an address holding a 20 byte value + Address CreateRandomAddress(); + bool CreateAddress(const Address& address); + bool Exist(const Address& address); + + static Address CreateContractAddress(const Address& owner); + + static std::string AddressToHex(const Address& address); + static Address HexToAddress(const std::string& address); + + static uint256_t AddressToSHAKey(const Address& address); + + private: + std::set
users_; +}; + +} // namespace contract +} // namespace resdb diff --git a/chain/storage/txn_memory_db_test.cpp b/platform/consensus/ordering/fides/executor/manager/address_manager_test.cpp similarity index 60% rename from chain/storage/txn_memory_db_test.cpp rename to platform/consensus/ordering/fides/executor/manager/address_manager_test.cpp index 1ff726dcd..cb7d7454c 100644 --- a/chain/storage/txn_memory_db_test.cpp +++ b/platform/consensus/ordering/fides/executor/manager/address_manager_test.cpp @@ -23,49 +23,36 @@ * */ -#include "database/txn_memory_db.h" +#include "service/contract/executor/manager/address_manager.h" -#include +#include #include -#include "common/test/test_macros.h" +#include "eEVM/util.h" namespace resdb { +namespace contract { 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(AddressManagerTest, CreateAddress) { + Address address = AddressManager().CreateRandomAddress(); + std::array raw; + eevm::to_big_endian(address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, address); } -TEST(TxnMemoryDBTest, GetSecondValue) { - Request request; - request.set_seq(1); - request.set_data("test"); - - TxnMemoryDB db; - db.Put(std::make_unique(request)); +TEST(AddressManagerTest, CreateContractAddress) { + Address address = AddressManager().CreateRandomAddress(); - request.set_seq(1); - request.set_data("test_1"); - db.Put(std::make_unique(request)); + Address contract_address = AddressManager::CreateContractAddress(address); - EXPECT_THAT(db.Get(1), Pointee(EqualsProto(request))); + std::array raw; + eevm::to_big_endian(contract_address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, contract_address); } } // namespace - +} // namespace contract } // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/committer_context.cpp b/platform/consensus/ordering/fides/executor/manager/committer_context.cpp new file mode 100644 index 000000000..7d124aaf8 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/committer_context.cpp @@ -0,0 +1,67 @@ +/* + * 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 "service/contract/executor/manager/committer_context.h" + +#include "glog/logging.h" + +namespace resdb { +namespace contract { + +ExecutionContext::ExecutionContext(const ContractExecuteInfo & info ) { + info_ = std::make_unique(info); +} + +const ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() const { + return info_.get(); +} + +ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() { + return info_.get(); +} + +void ExecutionContext::SetResult(std::unique_ptr result) { + result_ = std::move(result); +} + +bool ExecutionContext::IsRedo() { + return is_redo_; +} + +void ExecutionContext::SetRedo(){ + is_redo_++; +} + +int ExecutionContext::RedoTime() { + return is_redo_; +} + + +std::unique_ptr ExecutionContext::FetchResult() { + return std::move(result_); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/committer_context.h b/platform/consensus/ordering/fides/executor/manager/committer_context.h new file mode 100644 index 000000000..2a693338e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/committer_context.h @@ -0,0 +1,53 @@ +/* + * 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 "service/contract/executor/manager/contract_committer.h" + +namespace resdb { +namespace contract { + +class ExecutionContext { +public: + ExecutionContext(const ContractExecuteInfo & info ) ; + const ContractExecuteInfo * GetContractExecuteInfo() const; + ContractExecuteInfo * GetContractExecuteInfo(); + + void SetRedo(); + bool IsRedo(); + int RedoTime(); + + void SetResult(std::unique_ptr result); + std::unique_ptr FetchResult(); + + private: + int is_redo_ = 0; + std::unique_ptr result_; + std::unique_ptr info_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/concurrency_controller.cpp b/platform/consensus/ordering/fides/executor/manager/concurrency_controller.cpp new file mode 100644 index 000000000..db4aa9efe --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/concurrency_controller.cpp @@ -0,0 +1,17 @@ +#include "service/contract/executor/manager/concurrency_controller.h" + +#include + +namespace resdb { +namespace contract { + +ConcurrencyController::ConcurrencyController(DataStorage * storage) : storage_(storage){} + +const DataStorage * ConcurrencyController::GetStorage() const { + return storage_; +} + + + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/concurrency_controller.h b/platform/consensus/ordering/fides/executor/manager/concurrency_controller.h new file mode 100644 index 000000000..c0b84c247 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/concurrency_controller.h @@ -0,0 +1,48 @@ +#pragma once + +#include "service/contract/executor/manager/data_storage.h" + +#include +#include + +namespace resdb { +namespace contract { + +enum State{ + LOAD = 0, + STORE = 1, + REMOVE = 2, +}; + +struct Data{ + State state; + uint256_t data; + int64_t version; + uint256_t old_data; + Data(){} + Data(const State& state):state(state){} + Data(const State& state, const uint256_t& data, int64_t version = 0) + :state(state), data(data), version(version){} + Data(const State& state, const uint256_t& data, int64_t version, const uint256_t& old_data) + :state(state), data(data), version(version), old_data(old_data){} + bool operator != (const Data& d) const{ + return d.state != this->state || d.data != this->data || d.version != this->version; + } +}; + +class ConcurrencyController { + public: + ConcurrencyController(DataStorage * storage); + + typedef std::map> ModifyMap; + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_) = 0; + + const DataStorage * GetStorage() const; + + protected: + DataStorage * storage_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_committer.h b/platform/consensus/ordering/fides/executor/manager/contract_committer.h new file mode 100644 index 000000000..221789664 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_committer.h @@ -0,0 +1,77 @@ +/* + * 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 "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/contract_execute_info.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "eEVM/address.h" + +namespace resdb { +namespace contract { + +/* +struct ContractExecuteInfo { + eevm::Address caller_address; + eevm::Address contract_address; + std::string func_addr; + Params func_params; + int64_t commit_id; + uint64_t user_id; + ContractExecuteInfo(){} + ContractExecuteInfo( + eevm::Address caller_address, + eevm::Address contract_address, + std::string func_addr, + Params func_params, + int64_t commit_id): caller_address(caller_address), contract_address(contract_address), func_addr(func_addr), func_params(func_params), commit_id(commit_id){} +}; +*/ + +class ContractCommitter { + public: + ContractCommitter() = default; + virtual ~ContractCommitter() = default; + + virtual std::vector> ExecContract(std::vector& request) = 0; + + virtual void AsyncExecContract(std::vector& request){}; + + virtual absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) = 0; + + virtual void SetExecuteCallBack(std::function)> ){}; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_deployer.cpp b/platform/consensus/ordering/fides/executor/manager/contract_deployer.cpp new file mode 100644 index 000000000..a866329a3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_deployer.cpp @@ -0,0 +1,186 @@ +/* + * 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 "service/contract/executor/manager/contract_deployer.h" + +#include "service/contract/executor/manager/address_manager.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace { + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); + // LOG(ERROR)<<"add arg:"<& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +} + +ContractDeployer::ContractDeployer(ContractCommitter * committer, GlobalState * gs) + : committer_(committer), gs_(gs){} + +std::string ContractDeployer::GetFuncAddress(const Address& contract_address, + const std::string& func_name) { + return func_address_[contract_address][func_name]; +} + +void ContractDeployer::SetFuncAddress(const Address& contract_address, + const FuncInfo& func) { + func_address_[contract_address][func.func_name()] = func.hash(); +} + +Address ContractDeployer::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info) { + const auto contract_address = + AddressManager::CreateContractAddress(owner_address); + + auto contract_constructor = eevm::to_bytes(deploy_info.contract_bin()); + for (const std::string& param : deploy_info.init_param()) { + AppendArgToInput(contract_constructor, param); + } + + try { + auto contract = gs_->create(contract_address, 0u, contract_constructor); + absl::StatusOr result = committer_->ExecContract( + owner_address, contract_address, + "", + {}, gs_); + + if(result.ok()){ + // set the initialized class context code. + contract.acc.set_code(eevm::to_bytes(*result)); + + for (const auto& info : deploy_info.func_info()) { + SetFuncAddress(contract_address, info); + } + //LOG(ERROR)<<"contract create:"<remove(contract_address); + return 0; + } + } catch (...) { + LOG(ERROR) << "Deploy throw expection"; + return 0; + } +} + + +bool ContractDeployer::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, const Address& contract_address) { + LOG(ERROR)<<"deploy address:"<create(contract_address, 0u, contract_constructor); + + LOG(ERROR)<<"create:"; + absl::StatusOr result = committer_->ExecContract( + owner_address, contract_address, + "", + {}, gs_); + + LOG(ERROR)<<"deploy result:"<remove(contract_address); + return false; + } + } catch (...) { + LOG(ERROR) << "Deploy throw expection"; + return false; + } +} + +Address ContractDeployer::DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params){ + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json["bin"]); + + for (auto& func : contract_json["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + for(const uint256_t& param : init_params){ + deploy_info.add_init_param(U256ToString(param)); + } + + return DeployContract(owner_address, deploy_info); +} + +bool ContractDeployer::DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params, const Address& contract_address){ + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json["bin"]); + + for (auto& func : contract_json["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + for(const uint256_t& param : init_params){ + deploy_info.add_init_param(U256ToString(param)); + } + + return DeployContract(owner_address, deploy_info, contract_address); +} + + +absl::StatusOr ContractDeployer::GetContract( + const Address& address) { + if (!gs_->Exists(address)) { + return absl::InvalidArgumentError("Contract not exist."); + } + + return gs_->get(address); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_deployer.h b/platform/consensus/ordering/fides/executor/manager/contract_deployer.h new file mode 100644 index 000000000..0a74e7501 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_deployer.h @@ -0,0 +1,64 @@ +/* + * 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 "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +class ContractDeployer { + public: + ContractDeployer(ContractCommitter * committer, GlobalState * gs); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + bool DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, const Address& contract_address); + + Address DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params); + bool DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params, const Address& contract_address); + + absl::StatusOr GetContract(const Address& address); + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + + private: + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + ContractCommitter* committer_; + GlobalState* gs_; + std::map> func_address_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_deployer_test.cpp b/platform/consensus/ordering/fides/executor/manager/contract_deployer_test.cpp new file mode 100644 index 000000000..41be4ae2c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_deployer_test.cpp @@ -0,0 +1,124 @@ +/* + * 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 "service/contract/executor/manager/contract_deployer.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/test_committer.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +class ContractDeployerTest : public Test { + public: + ContractDeployerTest() : owner_address_(get_random_address()) { + + storage_ = std::make_unique(); + gs_ = std::make_unique(storage_.get()); + execotor_ = std::make_unique(storage_.get(), gs_.get()); + + + + LOG(ERROR)<<"owner:"< storage_; + std::unique_ptr gs_; + std::unique_ptrexecotor_; +}; + +TEST_F(ContractDeployerTest, NoContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + auto account = deployer.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + deployer.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContractFromJson) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + Address contract_address = + deployer.DeployContract(owner_address_, contract_json_, {1000}); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_executor.cpp b/platform/consensus/ordering/fides/executor/manager/contract_executor.cpp new file mode 100644 index 000000000..2b8a4609e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_executor.cpp @@ -0,0 +1,103 @@ +/* + * 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 "service/contract/executor/manager/contract_executor.h" + +//#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); +} + +void AppendArgToInput(std::vector& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +absl::StatusOr ContractExecutor::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + + std::vector inputs; + if(!func_addr.empty()){ + inputs = eevm::to_bytes(func_addr); + } + for (const std::string& param : func_param.param()) { + AppendArgToInput(inputs, param); + } + + auto result = Execute(caller_address, contract_address, inputs, state); + + if (result.ok()) { + return eevm::to_hex_string(*result); + } + else { + LOG(ERROR)<<"execute fail:"<> ContractExecutor::Execute( + const Address& caller_address, const Address& contract_address, + const std::vector& input, + EVMState * state) { + // Ignore any logs produced by this transaction + eevm::NullLogHandler ignore; + eevm::Transaction tx(caller_address, ignore); + + + // Record a trace to aid debugging + eevm::Trace tr; + eevm::Processor p(*state); + + // Run the transaction + try { + const auto exec_result = + p.run(tx, caller_address, state->get(contract_address), input, 0u, &tr); + + if (exec_result.er != eevm::ExitReason::returned) { + // Print the trace if nothing was returned + if (exec_result.er == eevm::ExitReason::threw) { + return absl::InternalError( + fmt::format("Execution error: {}", exec_result.exmsg)); + } + return absl::InternalError("Deployment did not return"); + } + return exec_result.output; + } catch (...) { + return absl::InternalError(fmt::format("Execution error:")); + } +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_executor.h b/platform/consensus/ordering/fides/executor/manager/contract_executor.h new file mode 100644 index 000000000..eed37c969 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_executor.h @@ -0,0 +1,72 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/executor/manager/evm_state.h" +#include "service/contract/executor/manager/concurrency_controller.h" +#include "service/contract/executor/common/contract_execute_info.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +/* +struct ExecuteResp { + int ret; + absl::Status state; + int64_t commit_id; + Address contract_address; + ConcurrencyController :: ModifyMap rws; + std::string result; + int retry_time = 0; + uint64_t user_id = 0; + double runtime = 0; +}; +*/ + +class ContractExecutor { + public: + ContractExecutor() = default; + + ~ContractExecutor() = default; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + absl::StatusOr> Execute( + const Address& owner_address, const Address& contract_address, + const std::vector& func_params, EVMState * state); +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_executor_test.cpp b/platform/consensus/ordering/fides/executor/manager/contract_executor_test.cpp new file mode 100644 index 000000000..3c14e08c5 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_executor_test.cpp @@ -0,0 +1,367 @@ +/* + * 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 "service/contract/executor/manager/multi_contract_executor.h" +#include "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +uint256_t GetAddressHash(uint256_t address){ + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + code[63]=1; + + uint8_t h[32]; + eevm::keccak_256(code.data(), static_cast(code.size()), h); + return eevm::from_big_endian(h, sizeof(h)); +} + + +class MultiContractExecutorTest : public Test { + public: + MultiContractExecutorTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + LOG(ERROR)<<"test dir:"< storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + LOG(ERROR)<<"owner address:"<Load(owner_key).first, 1000); + } + +/* + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 0); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 300); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + */ +} +/* + +TEST_F(MultiContractExecutorTest, ExecMultiContractNoConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + info.push_back(ContractExecuteInfo(transfer_receiver, contract_address, "", func_params, 0)); + } + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 200); + EXPECT_EQ(storage_ptr->Load(transfer3_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} + +TEST_F(MultiContractExecutorTest, ExecMultiContractHaveConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 800); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} +*/ + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_manager.cpp b/platform/consensus/ordering/fides/executor/manager/contract_manager.cpp new file mode 100644 index 000000000..f53aa9041 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_manager.cpp @@ -0,0 +1,209 @@ +/* + * 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 "service/contract/executor/manager/contract_manager.h" + +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/two_phase_committer.h" +//#include "service/contract/executor/manager/sequential_concurrency_committer.h" +#include "service/contract/executor/manager/ooo_committer.h" +#include "service/contract/executor/manager/two_phase_ooo_committer.h" +#include "service/contract/executor/manager/streaming_committer.h" +#include "service/contract/executor/manager/streaming_single_committer.h" +#include "service/contract/executor/manager/streaming_dq_committer.h" +#include "service/contract/executor/manager/x_committer.h" +#include "service/contract/executor/manager/x_verifier.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +ContractManager::ContractManager(std::unique_ptr storage, + int worker_num, Options op){ + storage_ = std::move(storage); + gs_ = std::make_unique(storage_.get()); + + switch(op){ + case TwoPL: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case TwoPLOOO: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case OOO: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case Streaming: + committer_ = std::make_unique(storage_.get(), gs_.get(), 500, nullptr, worker_num); + break; + case SCC: + //committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case SingleStreaming: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case MultiStreaming: + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, nullptr, worker_num); + break; + case XE: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + break; + case XEO: + committer_ = std::make_unique(storage_.get(), gs_.get(), worker_num); + //((XCommitter*)committer_.get())->UseOCC(); + break; + } + + deployer_ = std::make_unique(committer_.get(), gs_.get()); + + verifier_ = std::make_unique(storage_.get(), gs_.get(), worker_num); +} + +void ContractManager::SetExecuteCallBack(std::function resp)> func) { + committer_->SetExecuteCallBack(std::move(func)); +} + +Address ContractManager::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info) { + return deployer_->DeployContract(owner_address, deploy_info); +} + +bool ContractManager::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, + const Address& contract_address) { + return deployer_->DeployContract(owner_address, deploy_info, contract_address); +} + +absl::StatusOr ContractManager::GetContract(const Address& address) { + return deployer_->GetContract(address); +} + +std::vector> ContractManager::ExecContract(std::vector& execute_info) { + std::map> ct; + std::map cct; + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + + return committer_->ExecContract(execute_info); +} + +void ContractManager::AsyncExecContract(std::vector& execute_info) { + std::map> ct; + std::map cct; + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + + /* + cct[i]=0; + if(execute_info[i].func_params.param_size()>0){ + for(int j = 0; j < execute_info[i].func_params.param_size()-1; j++){ + auto& p = execute_info[i].func_params.param(j); + //LOG(ERROR)<<"get idx:"<2){ + for(auto id : it.second){ + cct[id]=1; + } + } + } + + int tot = 0, num = 0; + for(auto x : cct){ + if(x.second==1)num++; + tot++; + } + LOG(ERROR)<<"tot:"<AsyncExecContract(execute_info); +} + + +absl::StatusOr ContractManager::ExecContract( + const Address& caller_address, const Address& contract_address, + const Params& func_param) { + std::string func_addr = + deployer_->GetFuncAddress(contract_address, func_param.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << func_param.func_name(); + return absl::InvalidArgumentError("Func not exist."); + } + + + absl::StatusOr result = committer_->ExecContract( + caller_address, contract_address, + func_addr, + func_param, gs_.get()); + if(result.ok()){ + return *result; + } + return result.status(); +} + +bool ContractManager::VerifyContract( + std::vector& ordered_info, + std::vector rws_list) { + return true; + for(int i = 0; i < ordered_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(ordered_info[i].contract_address, ordered_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << ordered_info[i].func_params.func_name(); + ordered_info[i].contract_address = 0; + continue; + } + ordered_info[i].func_addr = func_addr; + } + return verifier_->VerifyContract(ordered_info, rws_list); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_manager.h b/platform/consensus/ordering/fides/executor/manager/contract_manager.h new file mode 100644 index 000000000..7e9c4035d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_manager.h @@ -0,0 +1,95 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_verifier.h" +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +class ContractManager { + public: + enum Options { + TwoPL = 1, + SCC = 2, + OOO = 3, + TwoPLOOO = 4, + Streaming = 5, + SingleStreaming = 6, + MultiStreaming = 7, + XE = 8, + XEO = 9, + }; + ContractManager(std::unique_ptr storage, + int worker_num = 2, Options op = TwoPL ); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + + bool DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, + const Address& contract_address); + + absl::StatusOr GetContract(const Address& address); + + absl::StatusOr ExecContract(const Address& caller_address, + const Address& contract_address, + const Params& func_param); + + std::vector> ExecContract( + std::vector& execute_info); + + void AsyncExecContract(std::vector& execute_info); + + void SetExecuteCallBack(std::function resp)> func); + + bool VerifyContract( + std::vector& ordered_info, + std::vector rws_list); + + private: + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptr committer_; + std::unique_ptr deployer_; + std::unique_ptr verifier_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_manager_test.cpp b/platform/consensus/ordering/fides/executor/manager/contract_manager_test.cpp new file mode 100644 index 000000000..759a24666 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_manager_test.cpp @@ -0,0 +1,259 @@ +/* + * 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 "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class ContractManagerTest : public Test { + public: + ContractManagerTest() : owner_address_(get_random_address()) { + LOG(ERROR)<<"owner:"<()); + auto account = manager.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractManagerTest, DeployContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractManagerTest, InitContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + LOG(ERROR)<<" deploy done:"<()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1000); + } + + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + // receiver 400 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 400); + } + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } +} + +TEST_F(ContractManagerTest, NoFunc) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf()"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_FALSE(result.ok()); + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/contract_verifier.cpp b/platform/consensus/ordering/fides/executor/manager/contract_verifier.cpp new file mode 100644 index 000000000..96df4fe89 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_verifier.cpp @@ -0,0 +1,53 @@ +/* + * 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 "service/contract/executor/manager/contract_verifier.h" + +namespace resdb { +namespace contract { + + +ContractVerifier:: ContractVerifier(){ + executor_ = std::make_unique(); +} + +ContractVerifier::~ContractVerifier(){ +} + +std::vector> ContractVerifier::ExecContract(std::vector& request ){ + return std::vector>(); +} + +absl::StatusOr ContractVerifier::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + + +} // namespace contract +} // namespace resdb + diff --git a/platform/consensus/ordering/fides/executor/manager/contract_verifier.h b/platform/consensus/ordering/fides/executor/manager/contract_verifier.h new file mode 100644 index 000000000..cb45fbdd7 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/contract_verifier.h @@ -0,0 +1,58 @@ +/* + * 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 "absl/status/statusor.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/contract_executor.h" + +namespace resdb { +namespace contract { + +class ContractVerifier : public ContractCommitter { + public: + ContractVerifier(); + virtual ~ContractVerifier(); + + virtual bool VerifyContract( + const std::vector& request_list, + const std::vector& rws_list) = 0; + + std::vector> ExecContract( + std::vector& request )override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) override; + + protected: + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/d_storage.cpp b/platform/consensus/ordering/fides/executor/manager/d_storage.cpp new file mode 100644 index 000000000..30b9b3a12 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/d_storage.cpp @@ -0,0 +1,164 @@ +#include "service/contract/executor/manager/d_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1024; + } + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex ) { + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + //LOG(ERROR)<<"store key:"< InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return std::make_pair(0,0); + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + //LOG(ERROR)<<"get version key:"<second.second; + return it->second.second; +} + +} + +int64_t D_Storage::Store(const uint256_t& key, const uint256_t& value, bool is_to_local_view) { + //LOG(ERROR)<<"store key:"< D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"< +#include + +#include "service/contract/executor/manager/data_storage.h" + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class D_Storage : public DataStorage { + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_[1024]; + mutable std::shared_mutex mutex_[1024]; + + std::map > g_s_[1024]; + mutable std::shared_mutex g_mutex_[1024]; + +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/data_storage.cpp b/platform/consensus/ordering/fides/executor/manager/data_storage.cpp new file mode 100644 index 000000000..43208b61e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/data_storage.cpp @@ -0,0 +1,74 @@ +#include "service/contract/executor/manager/data_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return 0; + return v%2048; + } +} + +int64_t DataStorage::Store(const uint256_t& key, const uint256_t& value, bool) { + int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + //LOG(ERROR)<<"store key:"< DataStorage::Load(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + //LOG(ERROR)<<"load key:"<second; +} + +bool DataStorage::Remove(const uint256_t& key, bool) { +int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + auto e = s.find(key); + if (e == s.end()) + return false; + s.erase(e); + return true; +} + +bool DataStorage::Exist(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + return s.find(key) != s.end(); +} + +int64_t DataStorage::GetVersion(const uint256_t& key, bool) const{ +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + auto it = s.find(key); + if( it == s.end()){ + return 0; + } + return it->second.second; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/data_storage.h b/platform/consensus/ordering/fides/executor/manager/data_storage.h new file mode 100644 index 000000000..1a52d8717 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/data_storage.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class DataStorage { + +public: + DataStorage() = default; + virtual ~DataStorage() = default; + + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local = false); + virtual std::pair Load(const uint256_t& key, bool is_local_view = false) const; + virtual bool Remove(const uint256_t& key, bool is_local = false); + virtual bool Exist(const uint256_t& key, bool is_local = false) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local = false) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local = false) {} + virtual void Flush(){}; + +protected: + std::map > s; + //std::map > s[4096]; + mutable std::shared_mutex mutex_[4096]; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/evm_state.h b/platform/consensus/ordering/fides/executor/manager/evm_state.h new file mode 100644 index 000000000..ccd0d9500 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/evm_state.h @@ -0,0 +1,23 @@ +#pragma once + +#include "eEVM/globalstate.h" + +namespace resdb { +namespace contract { + +class EVMState : public eevm::GlobalState { +public: + EVMState() = default; + virtual ~EVMState() = default; + +protected: + const eevm::Block& get_current_block() override { return block_; } + uint256_t get_block_hash(uint8_t offset) override { return 0; } + +private: + // Unused. + eevm::Block block_; +}; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/global_state.cpp b/platform/consensus/ordering/fides/executor/manager/global_state.cpp new file mode 100644 index 000000000..e10f02e96 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/global_state.cpp @@ -0,0 +1,50 @@ +#include "service/contract/executor/manager/global_state.h" + +#include + +namespace resdb { +namespace contract { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + GlobalState::GlobalState(DataStorage * storage) : storage_(storage) { + } + + bool GlobalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void GlobalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState GlobalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState GlobalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), GlobalView(storage_)}); + + return get(addr); + } + + const eevm::SimpleAccount& GlobalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void GlobalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/global_state.h b/platform/consensus/ordering/fides/executor/manager/global_state.h new file mode 100644 index 000000000..99a418966 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/global_state.h @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/manager/global_view.h" +#include "service/contract/executor/manager/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { + + class GlobalState : public EVMState{ + public: + using StateEntry = std::pair; + + public: + GlobalState(DataStorage* storage); + virtual ~GlobalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + DataStorage* storage_; + }; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/global_view.cpp b/platform/consensus/ordering/fides/executor/manager/global_view.cpp new file mode 100644 index 000000000..1cfbb5b41 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/global_view.cpp @@ -0,0 +1,25 @@ +#include "service/contract/executor/manager/global_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { + +GlobalView::GlobalView(DataStorage* storage) :storage_(storage){ } + +void GlobalView::store(const uint256_t& key, const uint256_t& value) { + storage_->Store(key, value); +} + +uint256_t GlobalView::load(const uint256_t& key) { + return storage_->Load(key).first; +} + +bool GlobalView::remove(const uint256_t& key) { + return storage_->Remove(key); +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/global_view.h b/platform/consensus/ordering/fides/executor/manager/global_view.h new file mode 100644 index 000000000..948ed4ffc --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/global_view.h @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include "service/contract/executor/manager/data_storage.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { + +class GlobalView : public eevm::Storage { + +public: + GlobalView(DataStorage* storage); + virtual ~GlobalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + +private: + DataStorage * storage_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/level_d_storage.cpp b/platform/consensus/ordering/fides/executor/manager/level_d_storage.cpp new file mode 100644 index 000000000..87502f016 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/level_d_storage.cpp @@ -0,0 +1,225 @@ +#include "service/contract/executor/manager/level_d_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + //LOG(ERROR)<<"store key:"< InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return std::make_pair(0,0); + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + + + + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + //LOG(ERROR)<<"get version key:"<second.second; + return it->second.second; +} + +std::string GetString(const uint256_t& key){ + return ""; +} + +uint256_t GetInt256(const std::string& value){ + return 0; +} + +std::string GetData(const uint256_t& value, int64_t version){ + std::string v_str = GetString(value); + std::string v((const char *)&version, sizeof(value)); + v.append(v_str, v_str.size()); + return v; +} + +int64_t GetVersion(const std::string& value){ + int64_t v; + memcpy(&v, value.c_str(), sizeof(v)); + return v; +} + +uint256_t GetValue(const std::string& value){ + return GetInt256(std::string(value.c_str()+sizeof(int64_t),value.size()-sizeof(int64_t))); +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + ResLevelDB* db, + std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + std::string old_value = db->GetValue(GetString(key)); + int64_t v = GetVersion(old_value); + db->SetValue(GetString(key), GetData(value, v+1)); + //LOG(ERROR)<<"store key:"< InternalLoad(const uint256_t& key, + ResLevelDB* db, + std::shared_mutex * mutex){ + std::shared_lock lock(*mutex); + std::string value = db->GetValue(GetString(key)); + return std::make_pair(GetValue(value), GetVersion(value)); +} + +bool InternalRemove(const uint256_t& key, + ResLevelDB* db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + db->SetValue(GetString(key), ""); + return true; +} + +bool InternalExist(const uint256_t& key, + ResLevelDB* db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + std::string value = db->GetValue(GetString(key)); + return value.empty(); +} + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + ResLevelDB* db, + std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + db->SetValue(GetString(key), GetData(value, version)); +} + +int64_t InternalGetVersion(const uint256_t& key, + ResLevelDB* db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + std::string value = db->GetValue(GetString(key)); + return GetVersion(value); +} + +} + +int64_t D_Storage::Store(const uint256_t& key, const uint256_t& value, bool is_to_local_view) { + //LOG(ERROR)<<"store key:"< D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"< +#include + +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class D_Storage : public DataStorage { + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_; + mutable std::shared_mutex mutex_; + + std::unique_ptr g_s_; + mutable std::shared_mutex g_mutex_; + +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/leveldb.cpp b/platform/consensus/ordering/fides/executor/manager/leveldb.cpp new file mode 100644 index 000000000..b540aed45 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/leveldb.cpp @@ -0,0 +1,30 @@ +#include "service/contract/executor/manager/leveldb.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +LevelDB::LevelDB(){ + db_ = std::make_unique("./"); + db_->SetBatchSize(10000); +} + +void LevelDB::Flush(){ + //LOG(ERROR)<<"flush"; + for(const auto& it : s){ + std::string addr = eevm::to_hex_string(it.first); + std::string value = eevm::to_hex_string(it.second.first); + //LOG(ERROR)<<"addr:"<SetValue(addr, std::string(buf, value.size()+sizeof(it.second.second))); + delete buf; + } +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/leveldb.h b/platform/consensus/ordering/fides/executor/manager/leveldb.h new file mode 100644 index 000000000..e76ca274f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/leveldb.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDB : public DataStorage { + +public: + LevelDB(); + virtual ~LevelDB() = default; + + virtual void Flush(); + +private: + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/leveldb_d_storage.cpp b/platform/consensus/ordering/fides/executor/manager/leveldb_d_storage.cpp new file mode 100644 index 000000000..e486d95fb --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/leveldb_d_storage.cpp @@ -0,0 +1,204 @@ + +#include "service/contract/executor/manager/leveldb_d_storage.h" +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1024; + } + + +void Write(const uint256_t& key, const uint256_t& value, int version, ResLevelDB * db){ + std::string addr = eevm::to_hex_string(key); + + const uint8_t* bytes = intx::as_bytes(value); + size_t sz = sizeof(value); + db->SetValue(addr, std::string((const char*)bytes, sz)); + return; +} + +uint256_t Read(const uint256_t& key, ResLevelDB * db){ + std::string addr = eevm::to_hex_string(key); + + std::string v = db->GetValue(addr); + if(v.empty()){ + return 0; + } + + uint8_t tmp[32] = {}; + memcpy(tmp, v.c_str(), 32); + return intx::le::load(tmp); +} + + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex, ResLevelDB * storage ) { + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); + if(storage) { + Write(key, value, version, storage); + } +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex, ResLevelDB * storage ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + if(storage) { + Write(key, value, v+1, storage); + } + //LOG(ERROR)<<"store key:"< InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex, ResLevelDB * storage){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()){ + if(storage) { + //LOG(ERROR)<<"load from db:"; + return std::make_pair(Read(key, storage), 0); + } + return std::make_pair(0,0); + } + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + //LOG(ERROR)<<"get version key:"<second.second; + return it->second.second; +} + +} + +LevelDB_D_Storage :: LevelDB_D_Storage(){ + db_ = std::make_unique("./"); +} + + +int64_t LevelDB_D_Storage::Store(const uint256_t& key, const uint256_t& value, bool is_to_local_view) { + //LOG(ERROR)<<"store key:"< LevelDB_D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"< +#include + +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDB_D_Storage : public DataStorage { +public: + LevelDB_D_Storage(); + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_[1024]; + mutable std::shared_mutex mutex_[1024]; + + std::map > g_s_[1024]; + mutable std::shared_mutex g_mutex_[1024]; + + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/leveldb_storage.cpp b/platform/consensus/ordering/fides/executor/manager/leveldb_storage.cpp new file mode 100644 index 000000000..0723fc5b9 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/leveldb_storage.cpp @@ -0,0 +1,113 @@ +#include "service/contract/executor/manager/leveldb_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return 0; + return v%2048; + } +} + +LevelDBStorage :: LevelDBStorage(){ + db_ = std::make_unique("./"); +} + +void LevelDBStorage::Write(const uint256_t& key, const uint256_t& value, int version){ + std::string addr = eevm::to_hex_string(key); + + const uint8_t* bytes = intx::as_bytes(value); + size_t sz = sizeof(value); + db_->SetValue(addr, std::string((const char*)bytes, sz)); + return; + + //LOG(ERROR)<<"addr:"<SetValue(addr, std::string(buf, sz+sizeof(version))); + LOG(ERROR)<<"write db"; + delete buf; +} + +uint256_t LevelDBStorage::Read(const uint256_t& key) const { + std::string addr = eevm::to_hex_string(key); + + std::string v = db_->GetValue(addr); + if(v.empty()){ + return 0; + } + + uint8_t tmp[32] = {}; + memcpy(tmp, v.c_str(), 32); + return intx::le::load(tmp); +} + + +int64_t LevelDBStorage::Store(const uint256_t& key, const uint256_t& value, bool) { + int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + //LOG(ERROR)<<"store key:"< LevelDBStorage::Load(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + //LOG(ERROR)<<"load key:"<second; +} + +bool LevelDBStorage::Remove(const uint256_t& key, bool) { +int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + auto e = s[idx].find(key); + if (e == s[idx].end()) + return false; + s[idx].erase(e); + return true; +} + +bool LevelDBStorage::Exist(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + return s[idx].find(key) != s[idx].end(); +} + +int64_t LevelDBStorage::GetVersion(const uint256_t& key, bool) const{ +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + auto it = s[idx].find(key); + if( it == s[idx].end()){ + return 0; + } + return it->second.second; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/leveldb_storage.h b/platform/consensus/ordering/fides/executor/manager/leveldb_storage.h new file mode 100644 index 000000000..8a1a38df2 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/leveldb_storage.h @@ -0,0 +1,39 @@ +#pragma once + +#include +#include + +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDBStorage : public DataStorage { + +public: + LevelDBStorage(); + virtual ~LevelDBStorage() = default; + + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local = false); + virtual std::pair Load(const uint256_t& key, bool is_local_view = false) const; + virtual bool Remove(const uint256_t& key, bool is_local = false); + virtual bool Exist(const uint256_t& key, bool is_local = false) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local = false) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local = false) {} + virtual void Flush(){}; + +private: + void Write(const uint256_t& key, const uint256_t& value, int version); + uint256_t Read(const uint256_t& key) const; + +protected: + std::map > s[4096]; + mutable std::shared_mutex mutex_[4096]; + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/local_state.cpp b/platform/consensus/ordering/fides/executor/manager/local_state.cpp new file mode 100644 index 000000000..e9cdc8318 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/local_state.cpp @@ -0,0 +1,73 @@ +#include "service/contract/executor/manager/local_state.h" + +#include + +namespace resdb { +namespace contract { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + LocalState::LocalState(ConcurrencyController * controller) : controller_(controller) { + } + + bool LocalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void LocalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState LocalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState LocalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), LocalView(controller_, 0)}); + return get(addr); + } + + const eevm::SimpleAccount& LocalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void LocalState::Set(const eevm::SimpleAccount& acc, int64_t commit_id) { + Insert({acc, LocalView(controller_, commit_id)}); + } + + void LocalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } + + bool LocalState::Flesh(const Address& addr, int commit_id) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + acc->second.second.Flesh(commit_id); + return true; + } + return false; + } + +/* + bool LocalState::Commit(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + return acc->second.second.Commit(); + } + return false; + } + */ + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/local_state.h b/platform/consensus/ordering/fides/executor/manager/local_state.h new file mode 100644 index 000000000..510b9fb50 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/local_state.h @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/manager/local_view.h" +#include "service/contract/executor/manager/concurrency_controller.h" +#include "service/contract/executor/manager/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { + + class LocalState : public EVMState { + public: + using StateEntry = std::pair; + + public: + LocalState(ConcurrencyController * controller); + virtual ~LocalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Flesh the local view to the controller with a commit id. + // Once all the contracts have fleshed their changes, they should call commit. + // Return false if contract not exists. + bool Flesh(const eevm::Address& addr, int commit_id); + // Commit the changes using the commit id from the flesh. + //bool Commit(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + void Set(const eevm::SimpleAccount& acc, int64_t commit_id); + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + ConcurrencyController * controller_; + }; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/local_view.cpp b/platform/consensus/ordering/fides/executor/manager/local_view.cpp new file mode 100644 index 000000000..816bfef7a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/local_view.cpp @@ -0,0 +1,78 @@ +#include "service/contract/executor/manager/local_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { + +LocalView::LocalView(ConcurrencyController * controller, int64_t commit_id) + :controller_(controller), + commit_id_(commit_id){ } + +void LocalView::store(const uint256_t& key, const uint256_t& value) { + //LOG(ERROR)<<"========= store key:"< load_value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(STORE, value, 0, 0)); + } + else { + const Data& data = local_changes_[key].back(); + if(data.state == LOAD){ + local_changes_[key].push_back(Data(STORE, value, data.version+1)); + } + else { + int64_t v = data.version; + local_changes_[key].pop_back(); + local_changes_[key].push_back(Data(STORE, value, v)); + } + } +} + +uint256_t LocalView::load(const uint256_t& key) { + //LOG(ERROR)<<"load key:"< value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(LOAD, value.first, value.second)); + return value.first; + } + return it->second.back().data; +} + +bool LocalView::remove(const uint256_t& key) { + store(key, 0); + return true; + + //local_changes_[key].push_back(Data(REMOVE)); + auto it = local_changes_.find(key); + if (it == local_changes_.end()){ + std::pair load_value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(REMOVE, 0, load_value.second, load_value.first)); + } + else { + const Data& data = local_changes_[key].back(); + if(data.state == LOAD){ + local_changes_[key].push_back(Data(REMOVE, 0, data.version+1)); + } + else { + int64_t v = data.version; + local_changes_[key].pop_back(); + local_changes_[key].push_back(Data(REMOVE, 0, v)); + } + } + + return true; +} + +void LocalView::Flesh(int64_t commit_id) { + commit_id_ = commit_id; + //LOG(ERROR)<<"commit push:"<PushCommit(commit_id, local_changes_); + local_changes_.clear(); +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/local_view.h b/platform/consensus/ordering/fides/executor/manager/local_view.h new file mode 100644 index 000000000..9a4731976 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/local_view.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { + +class LocalView : public eevm::Storage { + +public: + LocalView(ConcurrencyController * controller, int64_t commit_id); + virtual ~LocalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + + // for 2PL, once it is done, all the commit will be pushed to + // the controller to judge if it can be committed. + // During the flesh, all the changes will be removed. + void Flesh(int64_t commit_id); + // Commit the changes. If there is a conflict, return false. + // Make sure all other committers have pushed their changes before calling Commit. + //bool Commit(); + // Remove all the changes. + //void Abort(); + +private: + ConcurrencyController * controller_; + int64_t commit_id_; + std::map> local_changes_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/local_view_test.cpp b/platform/consensus/ordering/fides/executor/manager/local_view_test.cpp new file mode 100644 index 000000000..f05672a9f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/local_view_test.cpp @@ -0,0 +1,125 @@ +/* + * 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 "service/contract/executor/manager/local_view.h" +#include "service/contract/executor/manager/two_phase_controller.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +TEST(LocalViewTest, ViewChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); +} + +TEST(LocalViewTest, CommitChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + view.Flesh(0); + EXPECT_TRUE(controller.Commit(0)); + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + +TEST(LocalViewTest, CommitConflict) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view1(&controller, 0); + LocalView view2(&controller, 1); + + EXPECT_EQ(view1.load(address1), 2000) ; + view1.store(address1, 3000); + EXPECT_EQ(view1.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + EXPECT_EQ(view2.load(address1), 2000) ; + view2.store(address1, 4000); + EXPECT_EQ(view2.load(address1), 4000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + view1.Flesh(0); + view2.Flesh(1); + + EXPECT_FALSE(controller.Commit(1)); + EXPECT_TRUE(controller.Commit(0)); + + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/mock_d_storage.h b/platform/consensus/ordering/fides/executor/manager/mock_d_storage.h new file mode 100644 index 000000000..01d590af3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/mock_d_storage.h @@ -0,0 +1,47 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/manager/d_storage.h" + +namespace resdb { +namespace contract { + +class MockDStorage : public D_Storage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value, bool), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&, bool), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(void, Reset, (const uint256_t&, const uint256_t&, int64_t, bool), (override)); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/mock_data_storage.h b/platform/consensus/ordering/fides/executor/manager/mock_data_storage.h new file mode 100644 index 000000000..99717b1f4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/mock_data_storage.h @@ -0,0 +1,46 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/manager/data_storage.h" + +namespace resdb { +namespace contract { + +class MockStorage : public DataStorage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value, bool), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&, bool), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&, bool), (const, override)); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/ooo_committer.cpp b/platform/consensus/ordering/fides/executor/manager/ooo_committer.cpp new file mode 100644 index 000000000..20980d8ea --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/ooo_committer.cpp @@ -0,0 +1,119 @@ +/* + * 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 "service/contract/executor/manager/ooo_committer.h" + +#include "service/contract/executor/manager/local_state.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +OOOCommitter:: OOOCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num):gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->contract_address), + request->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->caller_address, + request->contract_address, + request->func_addr, + request->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->contract_address; + resp->commit_id = request->commit_id; + resp->user_id = request->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + } + else { + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +OOOCommitter::~OOOCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +std::vector> OOOCommitter::ExecContract( + std::vector& requests) { + + int process_num = requests.size(); + std::vector> resp_list; + + std::set commits; + resp_list.resize(process_num); + + for(auto& request: requests) { + request_queue_.Push(std::make_unique(request)); + } + + while(process_num){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + int64_t resp_commit_id = resp->commit_id; + resp_list[resp->commit_id-1]=std::move(resp); + process_num--; + } + + return resp_list; +} + +absl::StatusOr OOOCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/ooo_committer.h b/platform/consensus/ordering/fides/executor/manager/ooo_committer.h new file mode 100644 index 000000000..18cf258ef --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/ooo_committer.h @@ -0,0 +1,73 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/ooo_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +class OOOCommitter : public ContractCommitter { + public: + OOOCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~OOOCommitter(); + + std::vector> ExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + + const int worker_num_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/ooo_controller.cpp b/platform/consensus/ordering/fides/executor/manager/ooo_controller.cpp new file mode 100644 index 000000000..83f783e9b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/ooo_controller.cpp @@ -0,0 +1,35 @@ +#include "service/contract/executor/manager/ooo_controller.h" + +#include + +namespace resdb { +namespace contract { + +OOOController::OOOController(DataStorage * storage) : ConcurrencyController(storage){ +} + +OOOController::~OOOController(){} + +void OOOController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + for(const auto& it : local_changes){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + storage_->Store(it.first, op.data); + done = true; + break; + case REMOVE: + storage_->Remove(it.first); + done = true; + break; + } + } + } +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/ooo_controller.h b/platform/consensus/ordering/fides/executor/manager/ooo_controller.h new file mode 100644 index 000000000..172802738 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/ooo_controller.h @@ -0,0 +1,17 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +namespace resdb { +namespace contract { + +class OOOController : public ConcurrencyController { + public: + OOOController(DataStorage * storage); + ~OOOController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller.cpp b/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller.cpp new file mode 100644 index 000000000..b24fd9d0a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller.cpp @@ -0,0 +1,263 @@ +#include "service/contract/executor/manager/sequential_cc_controller.h" + +#include + +namespace resdb { +namespace contract { + +SequentialCCController::SequentialCCController(DataStorage * storage) : ConcurrencyController(storage){ + + for(int i = 0; i < window_size_; ++i){ + is_redo_.push_back(false); + } + + Clear(); + last_commit_id_ = 0; +} + +SequentialCCController::~SequentialCCController(){} + +void SequentialCCController::Clear(){ + last_commit_id_=0; + + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + is_redo_[i] = false; + } + for(int i = 0; i < 1024; ++i){ + commit_list_[i].clear(); + } + m_list_.clear(); +} + +std::vector& SequentialCCController::GetRedo(){ + return redo_; +} + +int SequentialCCController::GetHashKey(const uint256_t& address){ + // Get big-endian form + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + return v%128; +} + +void SequentialCCController::SetCallback(CallBack call_back) { + call_back_ = call_back; +} + +void SequentialCCController::RedoCommit(int64_t commit_id, int flag) { + if(is_redo_[commit_id]){ + return; + } + is_redo_[commit_id] = true; + redo_.push_back(commit_id); +} + +void SequentialCCController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + if(commit_id<=last_commit_id_){ + //assert(!changes_list_[commit_id].empty()); + changes_list_[commit_id] = local_changes; + //assert(Commit(commit_id)); + return; + } + + { + changes_list_[commit_id] = local_changes; + /* + for(const auto& it : local_changes){ + int hash_idx = GetHashKey(it.first); + //LOG(ERROR)<<"address:"<last_commit_id_){ + const auto & local_changes = changes_list_[commit_id]; + for(const auto& it : local_changes){ + int hash_idx = GetHashKey(it.first); + //LOG(ERROR)<<"address:"< new_commit_ids; + for(const auto& it : change_set){ + bool done = false; + const uint256_t& address = it.first; + int64_t new_v = 0; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + int64_t next_commit_id = Remove(address, commit_id, new_v); + //if(next_commit_id> 0){ + if(next_commit_id> 0 && next_commit_id <= last_commit_id_){ + new_commit_ids.insert(next_commit_id); + } + if(done && next_commit_id>last_commit_id_){ + if(IsRead(address, next_commit_id)){ + new_commit_ids.insert(next_commit_id); + } + } + } + Remove(commit_id); + for(int64_t redo_commit: new_commit_ids){ + RedoCommit(redo_commit, 1); + } + return true; +} + +bool SequentialCCController::CheckFirstCommit(const uint256_t& address, int64_t commit_id){ + int idx = GetHashKey(address); + //std::shared_lock lock(mutexs_[idx]); + //LOG(ERROR)<<"load idx:"<0); + + if(commit_set.front() < commit_id){ + //if(*commit_set.begin() < commit_id){ + // not the first candidate + //LOG(ERROR)<<"still have dep. commit id:"<GetVersion(address); + if(op.version != v){ + //LOG(ERROR)<<"check load:"< +#include +#include +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { + +class SequentialCCController : public ConcurrencyController { + public: + struct CallBack { + std::function redo_callback = nullptr; + std::function committed_callback = nullptr; + }; + + SequentialCCController(DataStorage * storage); + ~SequentialCCController(); + + void SetCallback(CallBack call_back); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + std::vector& GetRedo(); + + void Clear(); + + private: + bool CommitInternal(int64_t commit_id); + bool CheckCommit(int64_t commit_id); + bool CheckFirstCommit(const uint256_t& address, int64_t commit_id); + + const ModifyMap * GetChangeList(int64_t commit_id); + int64_t Remove(const uint256_t& address, int64_t commit_id, uint64_t v); + void Remove(int64_t commit_id); + + + std::function GetCommitCallBack(int64_t commit_id); + std::function GetRedoCallBack(int64_t commit_id); + + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + + int GetHashKey(const uint256_t& address); + + bool IsRead(const uint256_t& address, int64_t commit_id); + + private: + const int window_size_ = 1000; + mutable std::shared_mutex mutex_, mutexs_[1024]; + + std::map first_commit_; + std::atomic last_commit_id_; + + std::vector changes_list_; + std::map > commit_list_[1024]; + //std::map > commit_list_[1024]; + std::map m_list_; + + std::vector is_redo_; + CallBack call_back_; + + std::vector redo_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller_test.cpp b/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller_test.cpp new file mode 100644 index 000000000..8ee81a035 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/sequential_cc_controller_test.cpp @@ -0,0 +1,1056 @@ +/* + * 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 "service/contract/executor/manager/sequential_cc_controller.h" + +#include +#include +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +void GetData(const std::string& addr, const DataStorage& storage, ConcurrencyController::ModifyMap& changes){ + changes[HexToInt(addr)].push_back(Data(LOAD,storage.Load(HexToInt(addr)).first, storage.Load(HexToInt(addr)).second)); +} + +void SetData(const std::string& addr, int value, ConcurrencyController::ModifyMap& changes){ + changes[HexToInt(addr)].push_back(Data(STORE, value)); +} + +TEST(ContractDagManagerTest, PushOneCommit) { + + std::promise done; + std::future done_future = done.get_future(); + + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t){ + LOG(ERROR)<<"committed"; + done.set_value(true); + }; + + int64_t commit_id = 1; + + DataStorage storage; + SequentialCCController controller(&storage); + controller.SetCallback(call_back); + + SequentialCCController::ModifyMap changes; + GetData("0x123", storage, changes); + SetData("0x124", 1000, changes); + + controller.PushCommit(commit_id, changes); + + done_future.get(); + EXPECT_EQ(storage.Load(HexToInt("0x123")).first, 0); + EXPECT_EQ(storage.Load(HexToInt("0x124")).first, 1000); +} + +TEST(ContractDagManagerTest, PushTwoOOOCommit) { + + std::promise done; + std::future done_future = done.get_future(); + + int committed_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t){ + LOG(ERROR)<<"committed"; + committed_num++; + if(committed_num==2){ + done.set_value(true); + } + }; + + DataStorage storage; + SequentialCCController controller(&storage); + controller.SetCallback(call_back); + + { + int64_t commit_id = 2; + + + SequentialCCController::ModifyMap changes; + + SetData("0x123", 3000, changes); + SetData("0x124", 4000, changes); + + controller.PushCommit(commit_id, changes); + } + + { + int64_t commit_id = 1; + + SequentialCCController::ModifyMap changes; + SetData("0x123", 1000, changes); + SetData("0x124", 2000, changes); + + controller.PushCommit(commit_id, changes); + } + + done_future.get(); + EXPECT_EQ(storage.Load(HexToInt("0x123")).first, 3000); + EXPECT_EQ(storage.Load(HexToInt("0x124")).first, 4000); +} + +TEST(ContractDagManagerTest, PushTwoOOOCommitRAWBlock) { + + DataStorage storage; + SequentialCCController controller(&storage); + + std::promise done; + std::future done_future = done.get_future(); + + int committed1_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + if(committed1_done==1&&redo_num==1){ + done.set_value(true); + } + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==1){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==1){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + std::set done_list; + int redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + done_list.insert(commit_id); + if(done_list.size()==3&&redo_num==1){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 3); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + std::set done_list; + int redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + LOG(ERROR)<<"commit:"< done; + std::future done_future = done.get_future(); + std::set done_list; + int redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + done_list.insert(commit_id); + if(done_list.size()==3&&redo_num==2){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + redo_num++; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int redo_num = 0; + std::set done_list; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + done_list.insert(commit_id); + if(done_list.size()==3&&redo_num==1){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int redo_num = 0; + std::set done_list; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + done_list.insert(commit_id); + if(done_list.size()==3&&redo_num==2){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + redo_num++; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==0){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==0){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==0){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0,redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + if(commit_id==1){ + committed1_done = 1; + } + if(commit_id==2){ + committed2_done = 1; + } + if(committed1_done==1&&committed2_done==1&&redo_num==0){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< done; + std::future done_future = done.get_future(); + + int committed1_done = 0, committed2_done = 0, redo_num = 0; + SequentialCCController::CallBack call_back; + call_back.committed_callback = [&](int64_t commit_id){ + LOG(ERROR)<<"commit id ====:"<=1){ + done.set_value(true); + } + }; + + call_back.redo_callback = [&](int64_t commit_id){ + EXPECT_EQ(commit_id, 2); + redo_num=1; + LOG(ERROR)<<"redo:"< + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +/* +ExecutionContext::ExecutionContext(const ContractExecuteInfo & info ) { + info_ = std::make_unique(info); +} + +const ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() const { + return info_.get(); +} + +void ExecutionContext::SetResult(std::unique_ptr result) { + result_ = std::move(result); +} + +bool ExecutionContext::IsRedo() { + return is_redo_; +} + +void ExecutionContext::SetRedo(){ + is_redo_ = true; +} + +std::unique_ptr ExecutionContext::FetchResult() { + return std::move(result_); +} +*/ + +ExecutionState * SequentialConcurrencyCommitter::GetExecutionState() { + return &execution_state_; +} + +SequentialConcurrencyCommitter:: SequentialConcurrencyCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num):storage_(storage), gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); + is_stop_ = false; + + SequentialCCController::CallBack callback; + callback.redo_callback = std::bind(&SequentialConcurrencyCommitter::RedoCallBack, this, std::placeholders::_1, std::placeholders::_2); + callback.committed_callback = std::bind(&SequentialConcurrencyCommitter::CommitCallBack, this,std::placeholders::_1); + + controller_->SetCallback(callback); + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=1; + // request->SetResult(std::move(resp)); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + resp->ret = -1; + } + //if(!request->IsRedo()){ + resp_queue_.Push(std::move(resp)); + //} + } + })); + } +} + +SequentialConcurrencyCommitter::~SequentialConcurrencyCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void SequentialConcurrencyCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +void SequentialConcurrencyCommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* SequentialConcurrencyCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void SequentialConcurrencyCommitter::CommitCallBack(int64_t commit_id){ +} + +void SequentialConcurrencyCommitter::RedoCallBack(int64_t commit_id, int flag){ +} + +std::vector> SequentialConcurrencyCommitter::ExecContract( + std::vector& requests) { + + execution_state_.commit_time = 0; + execution_state_.redo_time = 0; + + int process_num = requests.size(); + std::vector> resp_list; + + std::set commits; + int id = 1; + resp_list.resize(process_num); + for(auto& request: requests) { + resp_list[id-1] = nullptr; + request.commit_id = id++; + auto context = std::make_unique(request); + AddTask(request.commit_id, std::move(context)); + commits.insert(request.commit_id); + } + + controller_->Clear(); + for(auto& request: requests) { + auto context_ptr = GetTaskContext(request.commit_id); + request_queue_.Push(std::make_unique(context_ptr)); + } + + auto cur_it = commits.begin(); + std::queue redo_list; + while(process_num){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + int64_t resp_commit_id = resp->commit_id; + //LOG(ERROR)<<"recv :"<commit_id-1]=std::move(resp); + if(cur_it == commits.end() || resp_commit_id < *cur_it){ + bool ret = controller_->Commit(resp_commit_id); + std::vector list = controller_->GetRedo(); + for(int64_t next_id : list){ + bool ret = controller_->Commit(next_id); + if(!ret){ + //redo_list.push(next_id); + auto context_ptr = GetTaskContext(next_id); + context_ptr->SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + } + assert(ret); + process_num--; + } + while(cur_it != commits.end() && resp_list[*cur_it-1] !=nullptr){ + int64_t commit_id = *cur_it; + bool ret = controller_->Commit(commit_id); + cur_it++; + if(!ret){ + resp_list[commit_id-1]=nullptr; + if(controller_->GetRedo().size()){ + //redo_list.push(commit_id); + + auto context_ptr = GetTaskContext(commit_id); + context_ptr->SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + } + else { + process_num--; + std::vector list = controller_->GetRedo(); + for(int64_t next_id : list){ + resp_list[next_id-1]=nullptr; + //redo_list.push(next_id); + auto context_ptr = GetTaskContext(next_id); + context_ptr->SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + } + } + } + + for(auto& request: requests) { + RemoveTask(request.commit_id); + } + + storage_->Flush(); + + return resp_list; +} + +absl::StatusOr SequentialConcurrencyCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer.h b/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer.h new file mode 100644 index 000000000..640993f25 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer.h @@ -0,0 +1,113 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/sequential_cc_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +/* +class ExecutionContext { +public: + ExecutionContext(const ContractExecuteInfo & info ) ; + const ContractExecuteInfo * GetContractExecuteInfo() const; + + void SetRedo(); + bool IsRedo(); + + void SetResult(std::unique_ptr result); + std::unique_ptr FetchResult(); + + private: + bool is_redo_ = false; + std::unique_ptr result_; + std::unique_ptr info_; +}; +*/ + + +struct ExecutionState{ + std::atomic commit_time; + int redo_time = 0; +}; + +class SequentialConcurrencyCommitter : public ContractCommitter { + public: + SequentialConcurrencyCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~SequentialConcurrencyCommitter(); + + std::vector> ExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + ExecutionState * GetExecutionState(); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + ExecutionContext* GetTaskContext(int64_t commit_id); + + void CommitCallBack(int64_t commit_id); + void RedoCallBack(int64_t commit_id, int flag); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + + std::map> context_list_; + + const int worker_num_; + ExecutionState execution_state_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer_test.cpp b/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer_test.cpp new file mode 100644 index 000000000..723c2c754 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/sequential_concurrency_committer_test.cpp @@ -0,0 +1,246 @@ +/* + * 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 "service/contract/executor/manager/sequential_concurrency_committer.h" + +#include + +#include "service/contract/executor/manager/mock_data_storage.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class SequentialConcurrencyCommitterTest : public Test { + public: + SequentialConcurrencyCommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["ERC20.sol:ERC20Token"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address) { + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value) { + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + })); + + EXPECT_CALL(*storage_, GetVersion).WillRepeatedly(Invoke([&](const uint256_t& key) { + return data_[key].second; + })); + + Init(); + } + + void Init() { + gs_ = std::make_unique(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get()); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + std::vector> ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + std::map> data_; + std::unique_ptr deployer_; +}; + +TEST_F(SequentialConcurrencyCommitterTest, ExecContract) { + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 1); + EXPECT_EQ(resp[0]->ret, 0); + } +} + +// get a +// a->b +TEST_F(SequentialConcurrencyCommitterTest, TwoTxnNoConflict) { + Address transfer_receiver = get_random_address(); + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + EXPECT_EQ(resp[0]->ret, 0); + EXPECT_EQ(HexToInt(resp[0]->result), 1000); + EXPECT_EQ(resp[1]->ret, 0); + EXPECT_EQ(HexToInt(resp[1]->result), 1); + } + //LOG(ERROR)<<"commit time:"<GetExecutionState()->commit_time<<" redo time:"<GetExecutionState()->redo_time; + EXPECT_EQ(committer_->GetExecutionState()->commit_time,2); + EXPECT_EQ(committer_->GetExecutionState()->redo_time,0); +} + +// a->b +// a->b +TEST_F(SequentialConcurrencyCommitterTest, TwoTxnConflict) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address) { + if(start){ + load_time[address]++; + } + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value) { + if(start) { + bool done = false; + while(!done){ + for(auto it : load_time){ + if(it.second>1){ + done = true; + break; + } + } + } + } + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + })); + + Init(); + start = true; + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + EXPECT_EQ(resp[0]->ret, 0); + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(resp[1]->ret, 0); + EXPECT_EQ(HexToInt(resp[1]->result), 1); + } + EXPECT_EQ(committer_->GetExecutionState()->commit_time,2); + EXPECT_EQ(committer_->GetExecutionState()->redo_time,1); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_committer.cpp b/platform/consensus/ordering/fides/executor/manager/streaming_committer.cpp new file mode 100644 index 000000000..55d0bf9a0 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_committer.cpp @@ -0,0 +1,339 @@ +/* + * 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 "service/contract/executor/manager/streaming_committer.h" + +#include +#include + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace streaming { + +ExecutionContext::ExecutionContext(const ContractExecuteInfo & info ) { + info_ = std::make_unique(info); +} + +const ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() const { + return info_.get(); +} + +void ExecutionContext::SetResult(std::unique_ptr result) { + result_ = std::move(result); +} + +bool ExecutionContext::IsRedo() { + return is_redo_; +} + +void ExecutionContext::SetRedo(){ + is_redo_++; +} + +int ExecutionContext::RedoTime() { + return is_redo_; +} + +std::unique_ptr ExecutionContext::FetchResult() { + return std::move(result_); +} + +ExecutionState * StreamingCommitter::GetExecutionState() { + return &execution_state_; +} + +StreamingCommitter:: StreamingCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + controller_ = std::make_unique(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id<<" param:"<< + // request->GetContractExecuteInfo()->func_params.DebugString(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + // request->SetResult(std::move(resp)); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + resp_queue_.Push(std::move(resp)); + } + })); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void StreamingCommitter::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +StreamingCommitter::~StreamingCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +void StreamingCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id%window_size_] = std::move(context); +} + +void StreamingCommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* StreamingCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id%window_size_].get(); +} + +void StreamingCommitter::CallBack(uint64_t commit_id){ + int idx = commit_id%window_size_; + if(call_back_){ + call_back_(std::move(resp_list_[idx])); + } + else { + resp_list_[idx] = nullptr; + } + is_done_[idx] = true; + + //LOG(ERROR)<<"current commit done call back commit id:"< lk(mutex_); + cv_.notify_all(); + } + //LOG(ERROR)<<"call back done:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return id_ - first_id_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + //LOG(ERROR)<<"recv :"< q; + q.push(resp_commit_id); + while(!q.empty()){ + int64_t next_id = q.front(); + q.pop(); + + //LOG(ERROR)<<"!!!!!! commit retry:"<Commit(next_id); + if(!ret){ + //LOG(ERROR)<<"retry:"<SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + else { + CallBack(next_id); + } + std::vector next_commit = controller_->GetRedo(); + for(int64_t new_next : next_commit) { + if(next_id == new_next){ + continue; + } + q.push(new_next); + //LOG(ERROR)<<"get retry next:"<commit_id; + //LOG(ERROR)<<" !!!!! commit new resp:"<Commit(current_commit_id); + if(!ret){ + //LOG(ERROR)<<"redo size:"<GetRedo().size(); + if(controller_->GetRedo().size()){ + assert(controller_->GetRedo()[0] == current_commit_id); + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + } + else { + std::vector list = controller_->GetRedo(); + assert(list.empty()); + //LOG(ERROR)<<"commit done:"<SetRedo(); + request_queue_.Push(std::make_unique(context_ptr)); + } + CallBack(current_commit_id); + } + last_id_++; + } +} + +void StreamingCommitter::AsyncExecContract(std::vector& requests) { + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + + int cur_idx = id_%window_size_; + assert(id_>=last_id_); + + request.commit_id = id_++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + + //LOG(ERROR)<<"execute:"<(context_ptr)); + } + + return ; +} + +absl::StatusOr StreamingCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_committer.h b/platform/consensus/ordering/fides/executor/manager/streaming_committer.h new file mode 100644 index 000000000..d17ab010a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_committer.h @@ -0,0 +1,130 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/streaming_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace streaming { + +class ExecutionContext { +public: + ExecutionContext(const ContractExecuteInfo & info ); + const ContractExecuteInfo * GetContractExecuteInfo() const; + + void SetRedo(); + bool IsRedo(); + int RedoTime(); + + void SetResult(std::unique_ptr result); + std::unique_ptr FetchResult(); + + private: + int is_redo_ = 0; + std::unique_ptr result_; + std::unique_ptr info_; +}; + + +struct ExecutionState{ + std::atomic commit_time; + int redo_time = 0; +}; + +class StreamingCommitter : public ContractCommitter { + public: + StreamingCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingCommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + ExecutionState * GetExecutionState(); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + ExecutionState execution_state_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_controller.cpp b/platform/consensus/ordering/fides/executor/manager/streaming_controller.cpp new file mode 100644 index 000000000..d9d789eaf --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_controller.cpp @@ -0,0 +1,228 @@ +#include "service/contract/executor/manager/streaming_controller.h" + +#include + +namespace resdb { +namespace contract { +namespace streaming { + +StreamingController::StreamingController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size){ + Clear(); + last_commit_id_ = 0; +} + +StreamingController::~StreamingController(){} + +void StreamingController::Clear(){ + last_commit_id_=0; + + is_redo_.resize(window_size_); + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + is_redo_[i] = false; + } + + for(int i = 0; i < 1024; ++i){ + commit_list_[i].clear(); + } + m_list_.clear(); +} + +std::vector& StreamingController::GetRedo(){ + return redo_; +} + +int StreamingController::GetHashKey(const uint256_t& address){ + // Get big-endian form + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + return v%128; +} + +void StreamingController::RedoCommit(int64_t commit_id, int flag) { + if(is_redo_[commit_id%window_size_]){ + return; + } + is_redo_[commit_id%window_size_] = true; + redo_.push_back(commit_id); +} + +void StreamingController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + //LOG(ERROR)<<"push commit:"<last_commit_id_){ + const auto & local_changes = changes_list_[commit_id%window_size_]; + for(const auto& it : local_changes){ + int hash_idx = GetHashKey(it.first); + auto& commit_set = commit_list_[hash_idx][it.first]; + commit_set.push_back(commit_id); + } + } + + redo_.clear(); + bool ret = CommitInternal(commit_id); + if(commit_id == last_commit_id_+1){ + last_commit_id_++; + } + return ret; +} + +bool StreamingController::CommitInternal(int64_t commit_id){ + if(!CheckCommit(commit_id)){ + //LOG(ERROR)<<"check commit fail:"< new_commit_ids; + for(const auto& it : change_set){ + bool done = false; + const uint256_t& address = it.first; + int64_t new_v = 0; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + int64_t next_commit_id = Remove(address, commit_id, new_v); + //if(next_commit_id> 0){ + if(next_commit_id> 0 && next_commit_id <= last_commit_id_){ + new_commit_ids.insert(next_commit_id); + } + if(done && next_commit_id>last_commit_id_){ + if(IsRead(address, next_commit_id)){ + new_commit_ids.insert(next_commit_id); + } + } + } + Remove(commit_id); + for(int64_t redo_commit: new_commit_ids){ + RedoCommit(redo_commit, 1); + } + return true; +} + +bool StreamingController::CheckFirstCommit(const uint256_t& address, int64_t commit_id){ + int idx = GetHashKey(address); + //std::shared_lock lock(mutexs_[idx]); + //LOG(ERROR)<<"load idx:"<0); + + if(commit_set.front() < commit_id){ + //if(*commit_set.begin() < commit_id){ + // not the first candidate + //LOG(ERROR)<<"still have dep. commit id:"<GetVersion(address); + if(op.version != v){ + //LOG(ERROR)<<"check load:"< +#include +#include +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace streaming { + +class StreamingController : public ConcurrencyController { + public: + StreamingController(DataStorage * storage, int window_size); + ~StreamingController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + std::vector& GetRedo(); + + void Clear(); + + private: + bool CommitInternal(int64_t commit_id); + bool CheckCommit(int64_t commit_id); + bool CheckFirstCommit(const uint256_t& address, int64_t commit_id); + + const ModifyMap * GetChangeList(int64_t commit_id); + int64_t Remove(const uint256_t& address, int64_t commit_id, uint64_t v); + void Remove(int64_t commit_id); + + + std::function GetCommitCallBack(int64_t commit_id); + std::function GetRedoCallBack(int64_t commit_id); + + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + + int GetHashKey(const uint256_t& address); + + bool IsRead(const uint256_t& address, int64_t commit_id); + + private: + const int window_size_ = 1000; + std::atomic last_commit_id_; + + std::vector changes_list_; + std::map > commit_list_[1024]; + //std::map > commit_list_[1024]; + std::map m_list_; + + std::vector is_redo_; + + std::vector redo_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.cpp b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.cpp new file mode 100644 index 000000000..efa85f602 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.cpp @@ -0,0 +1,365 @@ +/* + * 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 "service/contract/executor/manager/streaming_dq_committer.h" + +#include +#include + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace streaming { + +namespace { +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; +} + +StreamingDQCommitter:: StreamingDQCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + //LOG(ERROR)<<"init window:"<(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + //LOG(ERROR)<<"========= start resp commit id:"<GetContractExecuteInfo()->commit_id; + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + resp->runtime = track.GetRunTime()*1000; + //LOG(ERROR)<<"retry:"<retry_time<<" run:"<runtime; + //LOG(ERROR)<<"run time:"<runtime; + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + resp_queue_.Push(std::move(resp)); + } + })); + cpu_set_t cpu_s; + CPU_ZERO(&cpu_s); + CPU_SET(i, &cpu_s); + int rc = pthread_setaffinity_np(workers_[i].native_handle(), sizeof(cpu_s), &cpu_s); + assert(rc==0); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void StreamingDQCommitter::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +StreamingDQCommitter::~StreamingDQCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +void StreamingDQCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id%window_size_] = std::move(context); +} + +void StreamingDQCommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* StreamingDQCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id%window_size_].get(); +} + +void StreamingDQCommitter::Clear(){ + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + id_ = 1; +} + +void StreamingDQCommitter::CallBack(uint64_t commit_id){ + int idx = commit_id%window_size_; + //LOG(ERROR)<<"call back:"<retry_time; + if(call_back_){ + call_back_(std::move(resp_list_[idx])); + } + else { + resp_list_[idx] = nullptr; + } + is_done_[idx] = true; + + //LOG(ERROR)<<"current commit done call back commit id:"< lk(mutex_); + cv_.notify_all(); + } + //LOG(ERROR)<<"call back done:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return id_ - first_id_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + //LOG(ERROR)<<"recv :"< q; + q.push(resp_commit_id); + while(!q.empty()){ + int64_t next_id = q.front(); + q.pop(); + + bool ret = controller_->Commit(next_id); + std::vector next_commit = controller_->GetRedo(); + //LOG(ERROR)<<"redo size:"<SetRedo(); + controller_->Clear(new_next); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + else { + q.push(new_next); + } + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<commit_id; + //LOG(ERROR)<<" !!!!! commit new resp:"<Commit(current_commit_id); + if(!ret){ + //LOG(ERROR)<<"redo size:"<GetRedo().size(); + if(controller_->GetRedo().size()){ + assert(controller_->GetRedo()[0] == current_commit_id); + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + controller_->Clear(current_commit_id); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + else { + std::vector list = controller_->GetRedo(); + for(int nd : list){ + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + controller_->Clear(nd); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<& requests) { + Clear(); + controller_->Clear(); + + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + int cur_idx = id_%window_size_; + assert(id_>=last_id_); + + request.commit_id = id_++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + + //LOG(ERROR)<<"execute:"<(context_ptr)); + } + + return ; +} + +absl::StatusOr StreamingDQCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.h b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.h new file mode 100644 index 000000000..f4eba1011 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer.h @@ -0,0 +1,106 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/streaming_dq_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace streaming { + +class StreamingDQCommitter : public ContractCommitter { + public: + StreamingDQCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingDQCommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + void Clear(); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; + std::atomic num_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer_test.cpp b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer_test.cpp new file mode 100644 index 000000000..39f49818a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_dq_committer_test.cpp @@ -0,0 +1,1100 @@ +/* + * 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 "service/contract/executor/manager/streaming_dq_committer.h" + +#include + +#include "service/contract/executor/manager/mock_d_storage.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + + +namespace resdb { +namespace contract { +namespace streaming { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class StreamingDQCommitterTest : public Test { + public: + StreamingDQCommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/kv.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["kv.sol:KV"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load:"<(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get(), 10); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + void ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + committer_->AsyncExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + std::map> data_, g_data_; + std::unique_ptr deployer_; +}; + +TEST_F(StreamingDQCommitterTest, ExecContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done.set_value(true); + }); + + { + ExecContract(info); + } + done_future.get(); +} + + +TEST_F(StreamingDQCommitterTest, ExecNoConflictContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictContractAhead2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(transfer_receiver3)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictContractAhead) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictPreCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictPreCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + +TEST_F(StreamingDQCommitterTest, ExecConflictCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + EXPECT_CALL(*storage_, Reset).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value, int64_t version, bool is_local) { + LOG(ERROR)<<"reset key:"< done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); +} + + +TEST_F(StreamingDQCommitterTest, ExecConflictCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + + bool start = false; + int store_time = 0; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(address==owner_key){ + //if(address==HexToInt("57815374765124470849362214049032306256895895577454318163812448629066198159199")){ + if(is_local){ + LOG(ERROR)<<" ==================== load store time:"< done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"!!!!! get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==6){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + +/* + uint256_t owner_key = HexToInt("57815374765124470849362214049032306256895895577454318163812448629066198159199"); + uint256_t transfer = HexToInt("27068505347551469483271880684020111790651525950042222728557705477772470047978"); + uint256_t transfer2 = HexToInt("78596659736029077736286538721783748383020833717077044719314309740706971846630"); + uint256_t transfer3 = HexToInt("4045740438897326892663995084801035374080699693195822287106415777594567914589"); + + EXPECT_EQ(g_data_[owner_key].first, HexToInt("400")); + EXPECT_EQ(g_data_[transfer].first, HexToInt("1000")); + EXPECT_EQ(g_data_[transfer2].first, HexToInt("300")); + EXPECT_EQ(g_data_[transfer3].first, HexToInt("800")); + + for(auto& key : g_data_){ + LOG(ERROR)<<"key:"< done; + std::future done_future = done.get_future(); + std::vector info; + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + + bool start = false; + int store_time = 0; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(address==owner_key){ + //if(address==HexToInt("57815374765124470849362214049032306256895895577454318163812448629066198159199")){ + if(is_local){ + LOG(ERROR)<<" ==================== load store time:"<2){ + while(store_time<9){ + sleep(1); + LOG(ERROR)<<" ==================== load store time:"< done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"!!!!! get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==6){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + +/* + LOG(ERROR)<<"owner:"< +#include +#include + +//#define CDebug + +namespace resdb { +namespace contract { +namespace streaming { + +StreamingDQController::StreamingDQController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + //ConcurrencyController(storage), window_size_(window_size), storage_(static_cast(storage)){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1<& StreamingDQController::GetRedo(){ + return redo_; +} + +std::vector& StreamingDQController::GetDone(){ + return done_; +} + +void StreamingDQController::CommitDone(int64_t commit_id) { + committed_[commit_id&window_size_] = true; + //LOG(ERROR)<<"commit done:"<GetVersion(it.first, false); +#ifdef CDebug + LOG(ERROR)<<"op log:"< new_commit_ids; + for(const auto& it : change_set){ + const uint256_t& address = it.first; + //LOG(ERROR)<<"get pre-op:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: +#ifdef CDebug + LOG(ERROR)<<"commit:"<Reset(it.first, op.data, op.version, false); + storage_->Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, true); + storage_->Reset(it.first, 0, op.version, false); + done = true; + break; + } + } + } + + CommitDone(commit_id); + last_commit_id_++; + return true; +} + +void StreamingDQController::RedoCommit(int64_t commit_id, int flag){ + if(is_redo_[commit_id]){ + return; + } + is_redo_[commit_id] = true; +#ifdef CDebug + LOG(ERROR)<<"====== add redo:"< +#include +#include +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "service/contract/executor/manager/d_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace streaming { + +class StreamingDQController : public ConcurrencyController { + public: + StreamingDQController(DataStorage * storage, int window_size); + ~StreamingDQController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + + std::vector& GetRedo(); + std::vector& GetDone(); + + typedef std::map > CommitList; + + void Clear(int64_t commit_id); + void Clear(); + + private: + + + bool PreCommit(int64_t commit_id); + bool PreCommitInternal(int64_t commit_id); + void MergeChangeList(int64_t commit_id); + + bool PostCommit(int64_t commit_id); + void RedoConflict(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + bool CheckPreCommit(int64_t commit_id); + + const ModifyMap * GetChangeList(int64_t commit_id); + void Remove(int64_t commit_id); + + + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + + void RollBackData(const uint256_t& address, const Data& data); + + bool IsRead(const uint256_t& address, int64_t commit_id); + + private: + int window_size_ = 2000; + + std::vector changes_list_,rechanges_list_; + + CommitList commit_list_[4196]; + int64_t last_commit_id_, current_commit_id_; + + CommitList pre_commit_list_[4196]; + int64_t last_pre_commit_id_; + + std::atomic is_redo_[4196]; + std::vector redo_; + bool wait_[4196], committed_[4196], finish_[4196]; + + //std::atomic is_done_[1024]; + std::vector done_; + + DataStorage * storage_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.cpp b/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.cpp new file mode 100644 index 000000000..a57d45bfd --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.cpp @@ -0,0 +1,104 @@ +/* + * 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 "service/contract/executor/manager/streaming_single_committer.h" + +#include +#include + +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace streaming { + +StreamingSingleCommitter:: StreamingSingleCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + call_back_(call_back) { + + executor_ = std::make_unique(); + + id_ = 1; +} + +void StreamingSingleCommitter::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +StreamingSingleCommitter::~StreamingSingleCommitter(){ +} + +void StreamingSingleCommitter::Execute(const ContractExecuteInfo& request){ + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request.caller_address, + request.contract_address, + request.func_addr, + request.func_params, gs_); + resp->state = ret.status(); + resp->contract_address = request.contract_address; + resp->commit_id = request.commit_id; + resp->user_id = request.user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + //LOG(ERROR)<<"commit :"<commit_id; + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + if(call_back_){ + call_back_(std::move(resp)); + } +} + +void StreamingSingleCommitter::AsyncExecContract(std::vector& requests) { + for(auto& request: requests) { + request.commit_id = id_++; + Execute(request); + } + + return ; +} + +absl::StatusOr StreamingSingleCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.h b/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.h new file mode 100644 index 000000000..a6d530b94 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/streaming_single_committer.h @@ -0,0 +1,80 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace streaming { + +class StreamingSingleCommitter : public ContractCommitter { + public: + StreamingSingleCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingSingleCommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } + +private: + void Execute(const ContractExecuteInfo& request); + + private: + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + int64_t id_; + + std::function)> call_back_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/test_committer.cpp b/platform/consensus/ordering/fides/executor/manager/test_committer.cpp new file mode 100644 index 000000000..e69912d0b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_committer.cpp @@ -0,0 +1,80 @@ +/* + * 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 "service/contract/executor/manager/test_committer.h" + +#include "glog/logging.h" +//#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +TestCommitter:: TestCommitter( + DataStorage* storage, + GlobalState * global_state):gs_(global_state) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); +} + +TestCommitter::~TestCommitter(){ +} + +std::vector> TestCommitter::ExecContract( + const std::vector& requests) { + + std::vector > resp_list; + for(const auto& request: requests){ + std::unique_ptr resp = std::make_unique(); + //auto start_time = GetCurrentTime(); + auto ret = ExecContract(request.caller_address, request.contract_address, + request.func_addr, + request.func_params, gs_); + resp->state = ret.status(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + } + else { + LOG(ERROR)<<"exec fail"; + resp->ret = -1; + } + resp->contract_address = request.contract_address; + resp->commit_id = request.commit_id; + + resp_list.push_back(std::move(resp)); + } + return resp_list; +} + +absl::StatusOr TestCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/test_committer.h b/platform/consensus/ordering/fides/executor/manager/test_committer.h new file mode 100644 index 000000000..0d98bafba --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_committer.h @@ -0,0 +1,63 @@ +/* + * 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 "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/test_controller.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" + +namespace resdb { +namespace contract { + +class TestCommitter : public ContractCommitter { + public: + TestCommitter( + DataStorage* storage, + GlobalState * global_state); + + virtual ~TestCommitter(); + + std::vector> ExecContract(const std::vector& request); + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + GlobalState* gs_; + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/test_controller.cpp b/platform/consensus/ordering/fides/executor/manager/test_controller.cpp new file mode 100644 index 000000000..97edd57ed --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_controller.cpp @@ -0,0 +1,35 @@ +#include "service/contract/executor/manager/test_controller.h" + +#include + +namespace resdb { +namespace contract { + +TestController::TestController(DataStorage * storage) : ConcurrencyController(storage){} + +void TestController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + for(auto it : local_changes){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return ; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/test_controller.h b/platform/consensus/ordering/fides/executor/manager/test_controller.h new file mode 100644 index 000000000..a4371033a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_controller.h @@ -0,0 +1,19 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +#include +#include + +namespace resdb { +namespace contract { + +class TestController : public ConcurrencyController { + public: + TestController(DataStorage * storage); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/test_data/BUILD b/platform/consensus/ordering/fides/executor/manager/test_data/BUILD new file mode 100644 index 000000000..6f9052157 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_data/BUILD @@ -0,0 +1 @@ +exports_files(["contract.json", "kv.json"]) diff --git a/platform/consensus/ordering/fides/executor/manager/test_data/compile.sh b/platform/consensus/ordering/fides/executor/manager/test_data/compile.sh new file mode 100644 index 000000000..dfd25e66f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_data/compile.sh @@ -0,0 +1,5 @@ +# sudo add-apt-repository ppa:ethereum/ethereum +# sudo apt-get update +# sudo apt-get install solc +solc --evm-version homestead --combined-json bin,hashes --pretty-json --optimize kv.sol > kv.json + diff --git a/platform/consensus/ordering/fides/executor/manager/test_data/contract.json b/platform/consensus/ordering/fides/executor/manager/test_data/contract.json new file mode 100644 index 000000000..e9c0d972f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_data/contract.json @@ -0,0 +1,19 @@ +{ + "contracts": + { + "ERC20.sol:ERC20Token": + { + "bin": "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a72315820e4ae92c4517e475d9f77ac0bfcd10463a09239f34734fc0ab4d7966450fb428464736f6c63430005100032", + "hashes": + { + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/manager/test_data/kv.json b/platform/consensus/ordering/fides/executor/manager/test_data/kv.json new file mode 100644 index 000000000..13fc7e73b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_data/kv.json @@ -0,0 +1,18 @@ +{ + "contracts": + { + "kv.sol:KV": + { + "bin": "608060405234801561001057600080fd5b506040516103d13803806103d18339818101604052602081101561003357600080fd5b50513360009081526020819052604090205561037d806100546000396000f3fe608060405234801561001057600080fd5b5060043610610073577c01000000000000000000000000000000000000000000000000000000006000350463239b83eb81146100785780633825d828146100c8578063beabacc8146100f4578063c2bc2efc1461012a578063cfc9c3f914610162575b600080fd5b6100b46004803603608081101561008e57600080fd5b50600160a060020a03813581169160208101358216916040820135169060600135610198565b604080519115158252519081900360200190f35b6100b4600480360360408110156100de57600080fd5b50600160a060020a038135169060200135610231565b6100b46004803603606081101561010a57600080fd5b50600160a060020a03813581169160208101359091169060400135610257565b6101506004803603602081101561014057600080fd5b5035600160a060020a03166102bf565b60408051918252519081900360200190f35b6100b46004803603606081101561017857600080fd5b50600160a060020a038135811691602081013590911690604001356102da565b600160a060020a038416600090815260208190526040812054828111156101d957600160a060020a0386166000908152602081905260409020805484900390555b61032081101561020657600160a060020a0385166000908152602081905260409020805484019055610225565b600160a060020a03841660009081526020819052604090208054840190555b50600195945050505050565b600160a060020a0382166000908152602081905260409020805482019055600192915050565b600160a060020a03831660009081526020819052604081205482101561029757600160a060020a0384166000908152602081905260409020805483900390555b50600160a060020a038216600090815260208190526040902080548201905560019392505050565b600160a060020a031660009081526020819052604090205490565b336000908152602081905260408120805483810390915561032081101561031e57600160a060020a038516600090815260208190526040902080548401905561033d565b600160a060020a03841660009081526020819052604090208054840190555b50600194935050505056fea265627a7a7231582073de5a372b480ae10296a8fccae0f9bc5adfb8d0ef56a2c198cac4b72fe6e8d264736f6c63430005100032", + "hashes": + { + "get(address)": "c2bc2efc", + "set(address,uint256)": "3825d828", + "transfer(address,address,uint256)": "beabacc8", + "transferif(address,address,address,uint256)": "239b83eb", + "transferto(address,address,uint256)": "cfc9c3f9" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/manager/test_data/kv.sol b/platform/consensus/ordering/fides/executor/manager/test_data/kv.sol new file mode 100644 index 000000000..f5bfed426 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/test_data/kv.sol @@ -0,0 +1,59 @@ +pragma solidity >= 0.5.0; + +// Transfer tokens from the contract owner +contract KV { + mapping (address => uint256) balances; + mapping (address => uint256) allow; + + constructor(uint256 s) public { + balances[msg.sender] = s; + } + + // Get the account balance of another account with address _owner + function get(address _owner) public view returns (uint256) { + return balances[_owner]; + } + + // Send _value amount of tokens to address _to + function set(address _to, uint256 _value) public returns (bool) { + uint256 values = balances[_to]; + balances[_to] = _value + values; + return true; + } + + // Send _value amount of tokens to address _to + function transfer(address _from, address _to, uint256 _value) public returns (bool) { + if (balances[_from] > _value) { + balances[_from] -= _value; + } + balances[_to] += _value; + return true; + } + + function transferif(address _from, address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[_from]; + + if (balances[_from] > _value) { + balances[_from] -= _value; + } + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + + function transferto(address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[msg.sender]; + balances[msg.sender] -= _value; + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + +} + diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_committer.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_committer.cpp new file mode 100644 index 000000000..2ab251d72 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_committer.cpp @@ -0,0 +1,168 @@ +/* + * 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 "service/contract/executor/manager/two_phase_committer.h" + +#include "service/contract/executor/manager/local_state.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +TwoPhaseCommitter:: TwoPhaseCommitter( + DataStorage* storage, + GlobalState * global_state, int worker_num):gs_(global_state),worker_num_(worker_num) { + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount(request->contract_address), request->commit_id); + + //LOG(ERROR)<<"worker:"<contract_address<<" commit id:"<commit_id; + std::unique_ptr resp = std::make_unique(); + //auto start_time = GetCurrentTime(); + auto ret = ExecContract(request->caller_address, request->contract_address, + request->func_addr, + request->func_params, &local_state); + resp->state = ret.status(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + local_state.Flesh(request->contract_address, request->commit_id); + } + else { + LOG(ERROR)<<"exec fail"; + resp->ret = -1; + } + //LOG(ERROR)<<"execute:"<contract_address = request->contract_address; + resp->commit_id = request->commit_id; + resp->user_id = request->user_id; + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +TwoPhaseCommitter::~TwoPhaseCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +std::vector> TwoPhaseCommitter::ExecContract( + std::vector& requests) { + + //LOG(ERROR)<<"executor contract size:"< fail_list; + std::map > responses; + + int do_time = 0; + //auto start_time = GetCurrentTime(); + do{ + controller_->Clear(); + + // process + int process_num = 0; + int receive_num = 0; + for(auto request: requests) { + if(fail_list.empty()){ + // first try + //LOG(ERROR)<<"try commit id:"<(request)); + process_num++; + } + else { + if(fail_list.find(request.commit_id) != fail_list.end()){ + // re-do + //LOG(ERROR)<<"re-do commit id:"<(request)); + process_num++; + } + } + } + + std::map > tmp_responses; + + while(process_num != receive_num){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + tmp_responses[resp->commit_id] = std::move(resp); + receive_num++; + } + + // commit + fail_list.clear(); + for(auto& resp_it: tmp_responses) { + auto& resp = resp_it.second; + if(resp->ret==0){ + //gs_->Flesh(resp->contract_address, resp->commit_id); + if(!controller_->Commit(resp->commit_id)){ + //LOG(ERROR)<<"commit fail, contract address:"<contract_address<<" commit id:"<commit_id; + fail_list.insert(resp->commit_id); + } + else { + //LOG(ERROR)<<"commit done, contract address:"<contract_address<<" commit id:"<commit_id; + } + } + resp->retry_time = do_time; + responses[resp_it.first] = std::move(resp); + } + do_time++; + }while(!fail_list.empty()); + + std::vector > resp_list; + for(auto& resp_it: responses) { + resp_list.push_back(std::move(resp_it.second)); + } + //if(do_time>1) + //LOG(ERROR)<<"commit time:"< TwoPhaseCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_committer.h b/platform/consensus/ordering/fides/executor/manager/two_phase_committer.h new file mode 100644 index 000000000..cf68d50d3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_committer.h @@ -0,0 +1,72 @@ +/* + * 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 "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/two_phase_controller.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" + +namespace resdb { +namespace contract { + +class TwoPhaseCommitter : public ContractCommitter { + public: + TwoPhaseCommitter( + DataStorage* storage, + GlobalState * global_state, int worker_num = 2); + + ~TwoPhaseCommitter(); + + std::vector> ExecContract(std::vector& request); + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + GlobalState* gs_; + std::vector workers_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + const int worker_num_; + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_committer_test.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_committer_test.cpp new file mode 100644 index 000000000..13df5808e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_committer_test.cpp @@ -0,0 +1,355 @@ +/* + * 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 "service/contract/executor/manager/two_phase_committer.h" + +#include + +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/address_manager.h" + +#include +#include + + + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +uint256_t GetAddressHash(uint256_t address){ + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + code[63]=1; + + uint8_t h[32]; + eevm::keccak_256(code.data(), static_cast(code.size()), h); + return eevm::from_big_endian(h, sizeof(h)); +} + + +class TwoPhaseCommitterTest : public Test { + public: + TwoPhaseCommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + LOG(ERROR)<<"test dir:"<(); + gs_ = std::make_unique(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get()); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const Params& func_param) { + std::string func_addr = + deployer_->GetFuncAddress(contract_address, func_param.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << func_param.func_name(); + return absl::InvalidArgumentError("Func not exist."); + } + + LOG(ERROR)<<"caller:"< result = committer_->ExecContract( + caller_address, contract_address, + func_addr, + func_param, gs_.get()); + if(result.ok()){ + return *result; + } + return result.status(); + } + + std::vector> ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i; + } + return committer_->ExecContract(execute_info); + } + + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr deployer_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + std::map> func_address_; +}; + +TEST_F(TwoPhaseCommitterTest, ExecContract) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + LOG(ERROR)<<"owner address:"<Load(owner_key).first, 1000); + } + + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + ExecContract(owner_address_, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 0); + + EXPECT_EQ(storage_->Load(transfer_key).first, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + ExecContract(owner_address_, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_->Load(transfer_key).first, 400); + EXPECT_EQ(storage_->Load(owner_key).first, 600); + } + + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + ExecContract(caller, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 600); + + EXPECT_EQ(storage_->Load(transfer_key).first, 400); + EXPECT_EQ(storage_->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + auto result = + ExecContract(transfer_receiver, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_->Load(transfer2_key).first, 0); + EXPECT_EQ(storage_->Load(transfer_key).first, 400); + EXPECT_EQ(storage_->Load(owner_key).first, 600); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + auto result = + ExecContract(transfer_receiver, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_->Load(transfer2_key).first, 100); + EXPECT_EQ(storage_->Load(transfer_key).first, 300); + EXPECT_EQ(storage_->Load(owner_key).first, 600); + } +} + +TEST_F(TwoPhaseCommitterTest, ExecMultiContractNoConflict) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + ExecContract(owner_address_, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_->Load(transfer_key).first, 400); + EXPECT_EQ(storage_->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + ExecContract(transfer_receiver, contract_address_, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + info.push_back(ContractExecuteInfo(transfer_receiver, contract_address_, "", func_params, 0)); + } + std::vector> resp = ExecContract(info); + EXPECT_EQ(storage_->Load(transfer2_key).first, 200); + EXPECT_EQ(storage_->Load(transfer3_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} + +TEST_F(TwoPhaseCommitterTest, ExecMultiContractHaveConflict) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::vector> resp = ExecContract(info); + EXPECT_EQ(storage_->Load(owner_key).first, 800); + EXPECT_EQ(storage_->Load(transfer_key).first, 100); + EXPECT_EQ(storage_->Load(transfer2_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_controller.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_controller.cpp new file mode 100644 index 000000000..e30d1e21c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_controller.cpp @@ -0,0 +1,112 @@ +#include "service/contract/executor/manager/two_phase_controller.h" + +#include + +namespace resdb { +namespace contract { + +TwoPhaseController::TwoPhaseController(DataStorage * storage) : ConcurrencyController(storage){} + +void TwoPhaseController::Clear(){ + std::unique_lock lock(mutex_); + changes_list_.clear(); + first_commit_.clear(); + + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + } +} + +void TwoPhaseController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + if(!changes_list_[commit_id].empty()){ + LOG(ERROR)<<"push record:"<second > commit_id){ + first_commit_[it.first] = commit_id; + } + } +} + +bool TwoPhaseController::CheckCommit(int64_t commit_id) { + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<second < commit_id){ + return false; + } + } + return true; +} + +bool TwoPhaseController::Commit(int64_t commit_id){ + if(!CheckCommit(commit_id)){ + return false; + } + + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return true; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_controller.h b/platform/consensus/ordering/fides/executor/manager/two_phase_controller.h new file mode 100644 index 000000000..b27b01cef --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_controller.h @@ -0,0 +1,33 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +#include +#include + +namespace resdb { +namespace contract { + +class TwoPhaseController : public ConcurrencyController { + public: + TwoPhaseController(DataStorage * storage); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + + bool Commit(int64_t commit_id); + + // Before each 2PL, make sure clear the data from the previous round. + void Clear(); + + private: + bool CheckCommit(int64_t commit_id); + + protected: + mutable std::shared_mutex mutex_; + std::vector changes_list_; + std::map first_commit_; + const int window_size_ = 1000; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_controller_test.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_controller_test.cpp new file mode 100644 index 000000000..a9546d52b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_controller_test.cpp @@ -0,0 +1,428 @@ +/* + * 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 "service/contract/executor/manager/two_phase_controller.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +void GetData(const std::string& addr, const DataStorage& storage, TwoPhaseController::ModifyMap& changes){ + changes[HexToInt(addr)].push_back(Data(LOAD,storage.Load(HexToInt(addr)).first, storage.Load(HexToInt(addr)).second)); +} + +void GetData(const uint256_t& addr, const DataStorage& storage, TwoPhaseController::ModifyMap& changes){ + changes[addr].push_back(Data(LOAD,storage.Load(addr).first, storage.Load(addr).second)); +} + +void SetData(const uint256_t& addr, int value, TwoPhaseController::ModifyMap& changes){ + changes[addr].push_back(Data(STORE, value)); +} + +void SetData(const std::string& addr, int value, TwoPhaseController::ModifyMap& changes){ + changes[HexToInt(addr)].push_back(Data(STORE, value)); +} + +TEST(TwoPhaseControllerTest, PushOneCommit) { + DataStorage storage; + TwoPhaseController controller(&storage); + + TwoPhaseController::ModifyMap changes; + + GetData("0x123", storage, changes); + SetData("0x124", 1000, changes); + + controller.PushCommit(0, changes); + + controller.Commit(0); + + EXPECT_EQ(storage.Load(HexToInt("0x123")).first, 0); + EXPECT_EQ(storage.Load(HexToInt("0x124")).first, 1000); +} + +TEST(TwoPhaseControllerTest, SkipLOAD) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + TwoPhaseController::ModifyMap changes; + + GetData("0x123", storage, changes); + SetData("0x124", 1000, changes); + + controller.PushCommit(0, changes); + + controller.Commit(0); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 1000); +} + +TEST(TwoPhaseControllerTest, Cover) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + TwoPhaseController::ModifyMap changes; + + GetData(address1, storage, changes); + SetData(address1, 1000, changes); + + controller.PushCommit(0, changes); + + EXPECT_TRUE(controller.Commit(0)); + + EXPECT_EQ(storage.Load(address1).first, 1000); +} + +TEST(TwoPhaseControllerTest, PushTwice) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + TwoPhaseController::ModifyMap changes; + + GetData(address1, storage, changes); + SetData(address1, 1000, changes); + + controller.PushCommit(0, changes); + controller.PushCommit(0, changes); + + EXPECT_TRUE(controller.Commit(0)); + + EXPECT_EQ(storage.Load(address1).first, 1000); +} + +TEST(TwoPhaseControllerTest, CommitTwice) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address1, 1000, changes); + + controller.PushCommit(0, changes); + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_TRUE(controller.Commit(0)); + + EXPECT_EQ(storage.Load(address1).first, 1000); +} + +TEST(TwoPhaseControllerTest, ConflictFull) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 1000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(1, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_FALSE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 1000); +} + +TEST(TwoPhaseControllerTest, ConflictCommit1Again) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(1, changes); + } + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 1000, changes); + + controller.PushCommit(0, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_FALSE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 1000); +} + +TEST(TwoPhaseControllerTest, ReadAfterWrite) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + uint256_t address3 = HexToInt("0x125"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + GetData(address2, storage, changes); + SetData(address3, 1000, changes); + + controller.PushCommit(1, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_FALSE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 3000); + EXPECT_FALSE(storage.Exist(address3)); +} + +TEST(TwoPhaseControllerTest, WriteAfterWrite) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + uint256_t address3 = HexToInt("0x125"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + GetData(address2, storage, changes); + SetData(address3, 1000, changes); + + controller.PushCommit(1, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_FALSE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 3000); + EXPECT_FALSE(storage.Exist(address3)); +} + +TEST(TwoPhaseControllerTest, ReadAfterRead) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + uint256_t address3 = HexToInt("0x125"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address3, 1000, changes); + + controller.PushCommit(1, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_TRUE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 2000); + EXPECT_EQ(storage.Load(address2).first, 3000); + EXPECT_EQ(storage.Load(address3).first, 1000); +} + +TEST(TwoPhaseControllerTest, WriteAfterRead) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + uint256_t address3 = HexToInt("0x125"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + SetData(address1, 500, changes); + SetData(address3, 1000, changes); + + controller.PushCommit(1, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_TRUE(controller.Commit(1)); + + EXPECT_EQ(storage.Load(address1).first, 500); + EXPECT_EQ(storage.Load(address2).first, 3000); + EXPECT_EQ(storage.Load(address3).first, 1000); +} + +TEST(TwoPhaseControllerTest, ReadAfterWriteAfterRead) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + uint256_t address2 = HexToInt("0x124"); + uint256_t address3 = HexToInt("0x125"); + uint256_t address4 = HexToInt("0x126"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + // commit 0: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address2, 3000, changes); + + controller.PushCommit(0, changes); + } + + // commit 1: + { + TwoPhaseController::ModifyMap changes; + SetData(address1, 500, changes); + SetData(address3, 1000, changes); + + controller.PushCommit(1, changes); + } + + // commit 2: + { + TwoPhaseController::ModifyMap changes; + GetData(address1, storage, changes); + SetData(address4, 2000, changes); + + controller.PushCommit(2, changes); + } + + EXPECT_TRUE(controller.Commit(0)); + EXPECT_TRUE(controller.Commit(1)); + EXPECT_FALSE(controller.Commit(2)); + + EXPECT_EQ(storage.Load(address1).first, 500); + EXPECT_EQ(storage.Load(address2).first, 3000); + EXPECT_EQ(storage.Load(address3).first, 1000); + EXPECT_FALSE(storage.Exist(address4)); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.cpp new file mode 100644 index 000000000..ad92f4661 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.cpp @@ -0,0 +1,159 @@ +/* + * 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 "service/contract/executor/manager/two_phase_ooo_committer.h" + +#include "service/contract/executor/manager/local_state.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +TwoPhaseOOOCommitter:: TwoPhaseOOOCommitter( + DataStorage* storage, + GlobalState * global_state, int worker_num):gs_(global_state),worker_num_(worker_num) { + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount(request->contract_address), request->commit_id); + + //LOG(ERROR)<<"worker:"<contract_address<<" commit id:"<commit_id; + std::unique_ptr resp = std::make_unique(); + //auto start_time = GetCurrentTime(); + auto ret = ExecContract(request->caller_address, request->contract_address, + request->func_addr, + request->func_params, &local_state); + resp->state = ret.status(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + local_state.Flesh(request->contract_address, request->commit_id); + } + else { + LOG(ERROR)<<"exec fail"; + resp->ret = -1; + } + //LOG(ERROR)<<"execute:"<contract_address = request->contract_address; + resp->commit_id = request->commit_id; + resp->user_id = request->user_id; + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +TwoPhaseOOOCommitter::~TwoPhaseOOOCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +std::vector> TwoPhaseOOOCommitter::ExecContract( + std::vector& requests) { + + //LOG(ERROR)<<"executor contract size:"< fail_list; + std::map > responses; + + int do_time = 0; + //auto start_time = GetCurrentTime(); + do{ + controller_->Clear(); + + // process + int process_num = 0; + int receive_num = 0; + for(auto request: requests) { + if(fail_list.empty()){ + // first try + //LOG(ERROR)<<"try commit id:"<(request)); + process_num++; + } + else { + if(fail_list.find(request.commit_id) != fail_list.end()){ + // re-do + //LOG(ERROR)<<"re-do commit id:"<(request)); + process_num++; + } + } + } + + std::map > tmp_responses; + + fail_list.clear(); + while(process_num != receive_num){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + receive_num++; + + if(!controller_->Commit(resp->commit_id)){ + //LOG(ERROR)<<"commit fail, contract address:"<contract_address<<" commit id:"<commit_id; + fail_list.insert(resp->commit_id); + } + + resp->retry_time = do_time; + responses[resp->commit_id] = std::move(resp); + } + + do_time++; + }while(!fail_list.empty()); + + std::vector > resp_list; + for(auto& resp_it: responses) { + resp_list.push_back(std::move(resp_it.second)); + } + //if(do_time>1) + //LOG(ERROR)<<"commit time:"< TwoPhaseOOOCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.h b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.h new file mode 100644 index 000000000..2dfdd4b2b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_committer.h @@ -0,0 +1,72 @@ +/* + * 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 "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/two_phase_ooo_controller.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" + +namespace resdb { +namespace contract { + +class TwoPhaseOOOCommitter : public ContractCommitter { + public: + TwoPhaseOOOCommitter( + DataStorage* storage, + GlobalState * global_state, int worker_num = 2); + + ~TwoPhaseOOOCommitter(); + + std::vector> ExecContract(std::vector& request); + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + GlobalState* gs_; + std::vector workers_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + const int worker_num_; + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.cpp b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.cpp new file mode 100644 index 000000000..5603bb706 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.cpp @@ -0,0 +1,86 @@ +#include "service/contract/executor/manager/two_phase_ooo_controller.h" + +#include + +namespace resdb { +namespace contract { + +TwoPhaseOOOController::TwoPhaseOOOController(DataStorage * storage) : ConcurrencyController(storage){} + +void TwoPhaseOOOController::Clear(){ + std::unique_lock lock(mutex_); + changes_list_.clear(); + first_commit_.clear(); + + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + } +} + +void TwoPhaseOOOController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + if(!changes_list_[commit_id].empty()){ + LOG(ERROR)<<"push record:"<GetVersion(address); + if(op.version != v){ + return false; + } + } + } + } + + return true; +} + +bool TwoPhaseOOOController::Commit(int64_t commit_id){ + if(!CheckCommit(commit_id)){ + return false; + } + + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return true; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.h b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.h new file mode 100644 index 000000000..5f9f52d3e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/two_phase_ooo_controller.h @@ -0,0 +1,33 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +#include +#include + +namespace resdb { +namespace contract { + +class TwoPhaseOOOController : public ConcurrencyController { + public: + TwoPhaseOOOController(DataStorage * storage); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + + bool Commit(int64_t commit_id); + + // Before each 2PL, make sure clear the data from the previous round. + void Clear(); + + private: + bool CheckCommit(int64_t commit_id); + + protected: + mutable std::shared_mutex mutex_; + std::vector changes_list_; + std::map first_commit_; + const int window_size_ = 1000; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/v_controller.cpp b/platform/consensus/ordering/fides/executor/manager/v_controller.cpp new file mode 100644 index 000000000..70e26674e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/v_controller.cpp @@ -0,0 +1,85 @@ +#include "service/contract/executor/manager/v_controller.h" + +#include + +namespace resdb { +namespace contract { + +VController::VController(DataStorage * storage) : ConcurrencyController(storage){ + + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + } +} + +VController::~VController(){} + +const ConcurrencyController::ModifyMap * VController::GetChangeList(int64_t commit_id) const { + return &changes_list_[commit_id]; +} + +void VController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + changes_list_[commit_id] = local_changes; +} + +bool VController::CheckCommit(int64_t commit_id) { + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<GetVersion(address); + //LOG(ERROR)<<"get version:"< new_commit_ids; + for(const auto& it : change_set){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return true; + +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/manager/v_controller.h b/platform/consensus/ordering/fides/executor/manager/v_controller.h new file mode 100644 index 000000000..9e1dfaf8f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/v_controller.h @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { + +class VController : public ConcurrencyController { + public: + VController(DataStorage * storage); + ~VController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + const ModifyMap * GetChangeList(int64_t commit_id) const; + +private: +bool CheckCommit(int64_t commit_id); + private: + const int window_size_ = 2000; + std::vector changes_list_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_committer.cpp b/platform/consensus/ordering/fides/executor/manager/x_committer.cpp new file mode 100644 index 000000000..584a8f015 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_committer.cpp @@ -0,0 +1,205 @@ +/* + * 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 "service/contract/executor/manager/x_committer.h" + +#include + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +XCommitter:: XCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num):storage_(storage), gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + TimeTrack track; + ExecutionContext * request = *request_ptr; + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + resp->runtime = track.GetRunTime()*1000; + //if(resp->retry_time>1){ + //LOG(ERROR)<<"runtime:"<runtime; + //} + } + else { + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + cpu_set_t cpu_s; + CPU_ZERO(&cpu_s); + CPU_SET(i+1, &cpu_s); + int rc = pthread_setaffinity_np(workers_[i].native_handle(), sizeof(cpu_s), &cpu_s); + assert(rc==0); + } +} + +XCommitter::~XCommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void XCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +void XCommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* XCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +std::vector> XCommitter::ExecContract( + std::vector& requests) { + + + controller_->Clear(); + int process_num = requests.size(); + std::vector> resp_list; + + int id = 1; + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + AddTask(request.commit_id, std::move(context)); + request_queue_.Push(std::make_unique(context_ptr)); + } + + while(process_num){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + int64_t resp_commit_id = resp->commit_id; + bool ret = controller_->Commit(resp_commit_id); + //LOG(ERROR)<<"resp commit:"<rws = *controller_->GetChangeList(resp_commit_id); + //LOG(ERROR)<<"get rws:"<retry_time; + resp_list.push_back(std::move(resp)); + RemoveTask(resp_commit_id); + process_num--; + continue; + } + /* + else { + auto context_ptr = GetTaskContext(resp_commit_id); + context_ptr->SetRedo(); + //LOG(ERROR)<<"redo:"<(context_ptr)); + } + */ + + std::vector list = controller_->GetRedo(); + //LOG(ERROR)<<"get list:"<SetRedo(); + //LOG(ERROR)<<"redo:"<(context_ptr)); + } + } + + return resp_list; +} + +absl::StatusOr XCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_committer.h b/platform/consensus/ordering/fides/executor/manager/x_committer.h new file mode 100644 index 000000000..21d3c596c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_committer.h @@ -0,0 +1,89 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/x_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +struct ExecutionState{ + std::atomic commit_time; + int redo_time = 0; +}; + +class XCommitter : public ContractCommitter { + public: + XCommitter( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~XCommitter(); + + std::vector> ExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + ExecutionContext* GetTaskContext(int64_t commit_id); + + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + + std::map> context_list_; + + const int worker_num_; + ExecutionState execution_state_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_committer_test.cpp b/platform/consensus/ordering/fides/executor/manager/x_committer_test.cpp new file mode 100644 index 000000000..f0a7f22e2 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_committer_test.cpp @@ -0,0 +1,251 @@ +/* + * 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 "service/contract/executor/manager/x_committer.h" + +#include + +#include "service/contract/executor/manager/mock_data_storage.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class XCommitterTest : public Test { + public: + XCommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["ERC20.sol:ERC20Token"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool) { + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value, bool ) { + int v = data_[key].second; + LOG(ERROR)<<"store:"<(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get()); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + std::vector> ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + std::map> data_; + std::unique_ptr deployer_; +}; + +/* +TEST_F(XCommitterTest, ExecContract) { + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 1); + EXPECT_EQ(resp[0]->ret, 0); + } +} +*/ + +// get a +// a->b +TEST_F(XCommitterTest, TwoTxnNoConflict) { + Address transfer_receiver = get_random_address(); + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + EXPECT_EQ(resp[0]->ret, 0); + EXPECT_EQ(HexToInt(resp[0]->result), 1000); + EXPECT_EQ(resp[1]->ret, 0); + EXPECT_EQ(HexToInt(resp[1]->result), 1); + } + //LOG(ERROR)<<"commit time:"<GetExecutionState()->commit_time<<" redo time:"<GetExecutionState()->redo_time; + //EXPECT_EQ(committer_->GetExecutionState()->commit_time,2); + //EXPECT_EQ(committer_->GetExecutionState()->redo_time,0); +} + +// a->b +TEST_F(XCommitterTest, TwoTxnConflict) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + /* + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address) { + if(start){ + load_time[address]++; + } + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value,bool) { + if(start) { + bool done = false; + while(!done){ + for(auto it : load_time){ + if(it.second>1){ + done = true; + break; + } + } + } + } + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + })); + + Init(); + start = true; + */ + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + EXPECT_EQ(resp[0]->ret, 0); + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(resp[1]->ret, 0); + EXPECT_EQ(HexToInt(resp[1]->result), 1); + } + //EXPECT_EQ(committer_->GetExecutionState()->commit_time,2); + //EXPECT_EQ(committer_->GetExecutionState()->redo_time,1); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_controller.cpp b/platform/consensus/ordering/fides/executor/manager/x_controller.cpp new file mode 100644 index 000000000..3443744f2 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_controller.cpp @@ -0,0 +1,241 @@ +#include "service/contract/executor/manager/x_controller.h" + +#include + +namespace resdb { +namespace contract { + +XController::XController(DataStorage * storage) : ConcurrencyController(storage){ + + for(int i = 0; i < window_size_; ++i){ + is_redo_.push_back(false); + } + + Clear(); + last_commit_id_ = 0; +} + +XController::~XController(){} + +void XController::Clear(){ + last_commit_id_=0; + + changes_list_.resize(window_size_); + pending_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + pending_list_[i].clear(); + is_redo_[i] = false; + } + commit_list_.clear(); + m_list_.clear(); +} + +void XController::UseOCC(){ + use_occ_ = true; +} + +std::vector& XController::GetRedo(){ + return redo_; +} + +int XController::GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1; + //return v%128; +} + +void XController::SetCallback(CallBack call_back) { + call_back_ = call_back; +} + +void XController::RedoCommit(int64_t commit_id) { + if(is_redo_[commit_id]){ + return; + } + is_redo_[commit_id] = true; + //LOG(ERROR)<<" add redo:"< list; + for(int64_t id : redo_list_){ + if(use_occ_){ + list.insert(id); + continue; + } + const auto& change_set = changes_list_[id]; + if(change_set.empty()){ + //LOG(ERROR)<<" no commit id record found:"< 0){ + ok = 0; + break; + } + } + if(ok){ + list.insert(id); + + //LOG(ERROR)<<"redo id:"< new_commit_ids; + for(const auto& it : change_set){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + { + storage_->Store(it.first, op.data); + done = true; + break; + } + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return true; +} + +bool XController::CheckCommit(int64_t commit_id) { + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<GetVersion(address); + //LOG(ERROR)<<"get version:"< +#include +#include +#include + +#include "service/contract/executor/manager/concurrency_controller.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { + +class XController : public ConcurrencyController { + public: + struct CallBack { + std::function redo_callback = nullptr; + std::function committed_callback = nullptr; + }; + + XController(DataStorage * storage); + ~XController(); + + void SetCallback(CallBack call_back); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + std::vector& GetRedo(); + + void Clear(); + const ModifyMap * GetChangeList(int64_t commit_id); + + void UseOCC(); + + private: + bool CommitInternal(int64_t commit_id); + bool CheckCommit(int64_t commit_id); + bool CheckFirstCommit(const uint256_t& address, int64_t commit_id); + + //int64_t Remove(const uint256_t& address, int64_t commit_id, uint64_t v); + void Remove(int64_t commit_id); + + + std::function GetCommitCallBack(int64_t commit_id); + std::function GetRedoCallBack(int64_t commit_id); + + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id); + + void CheckRedo(); + void AddRedo(int64_t commit_id); + + int GetHashKey(const uint256_t& address); + + bool IsRead(const uint256_t& address, int64_t commit_id); + + private: + const int window_size_ = 4096; + mutable std::shared_mutex mutex_, mutexs_[4096], cmutex_; + + std::map first_commit_; + std::atomic last_commit_id_; + + std::vector changes_list_, pending_list_; + std::map > commit_list_; + //std::map > commit_list_[2048]; + //std::map > commit_list_[1024]; + std::map m_list_; + + std::vector is_redo_; + CallBack call_back_; + + std::vector redo_; + std::set redo_list_; + std::map ref_; + bool use_occ_ = false; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_verifier.cpp b/platform/consensus/ordering/fides/executor/manager/x_verifier.cpp new file mode 100644 index 000000000..911b7572a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_verifier.cpp @@ -0,0 +1,188 @@ +/* + * 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 "service/contract/executor/manager/x_verifier.h" + +#include + +#include "service/contract/executor/manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + + +XVerifier:: XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num):storage_(storage), gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time++; + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +XVerifier::~XVerifier(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + + +bool XVerifier::VerifyContract( + const std::vector& request_list, + const std::vector& rws_list) { + std::vector d; + std::map > g; + std::map id; + + for(int i = 0; i < rws_list.size();++i){ + d.push_back(0); + for(auto it : rws_list[i]){ + const Address& address = it.first; + //LOG(ERROR)<<"i ="< q; + for(int i = 0; i < request_list.size();++i){ + if(d[i]==0){ + auto context = std::make_unique(request_list[i]); + context->GetContractExecuteInfo()->commit_id = i; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add:"<0){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + process_num--; + int resp_commit_id = resp->commit_id; + bool ret = controller_->Commit(resp_commit_id); + if(!ret){ + return false; + } + //LOG(ERROR)<<"verify:"<GetChangeList(resp_commit_id); + if(!RWSEqual(*rws, rws_list[resp_commit_id])){ + LOG(ERROR)<<"rws not equal"; + return false; + } + for(int n_id : g[resp_commit_id]){ + //LOG(ERROR)<<"next id:"<(request_list[n_id]); + context->GetContractExecuteInfo()->commit_id = n_id; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add next:"<first != it2->first){ + LOG(ERROR)<<"address not equal:"<first<<" "<first; + return false; + } + if(it1->second.size() != it2->second.size()){ + LOG(ERROR)<<"item size not equal:"<second.size()<<" "<second.size(); + return false; + } + for(int i = 0; i < it1->second.size(); i++){ + if(it1->second[i] != it2->second[i]){ + LOG(ERROR)<<"data not equal:"<second[i].data<<" "<second[i].data<<" addr:"<first; + return false; + } + } + } + return true; +} + + +} // namespace contract +} // namespace resdb + diff --git a/platform/consensus/ordering/fides/executor/manager/x_verifier.h b/platform/consensus/ordering/fides/executor/manager/x_verifier.h new file mode 100644 index 000000000..e3e24f8fc --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_verifier.h @@ -0,0 +1,72 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/v_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/contract_verifier.h" +#include "service/contract/executor/common/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { + +class XVerifier : public ContractVerifier { + public: + XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~XVerifier(); + + bool VerifyContract( + const std::vector& request_list, + const std::vector& rws_list); + + bool RWSEqual(const ConcurrencyController :: ModifyMap&a, const ConcurrencyController :: ModifyMap&b); + + private: + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::unique_ptr controller_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + const int worker_num_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/x_verifier_test.cpp b/platform/consensus/ordering/fides/executor/manager/x_verifier_test.cpp new file mode 100644 index 000000000..e95fd6171 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/x_verifier_test.cpp @@ -0,0 +1,229 @@ +/* + * 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 "service/contract/executor/manager/x_verifier.h" + +#include + +#include "service/contract/executor/manager/x_committer.h" +#include "service/contract/executor/manager/mock_data_storage.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_deployer.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class VVerifierTest : public Test { + public: + VVerifierTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/kv.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["kv.sol:KV"]; + storage_ = std::make_unique(); + v_storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool) { + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value, bool ) { + int v = data_[key].second; + LOG(ERROR)<<"store:"<(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get()); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + + + v_gs_ = std::make_unique(v_storage_.get()); + + verifier_ = std::make_unique(v_storage_.get(), v_gs_.get()); + + v_deployer_ = std::make_unique(verifier_.get(), v_gs_.get()); + v_deployer_->DeployContract(owner_address_, contract_json_, {1000}, contract_address_); + } + + std::vector> ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr v_storage_; + std::unique_ptr gs_, v_gs_; + std::unique_ptrcommitter_; + std::unique_ptr verifier_; + std::map> data_; + std::unique_ptr deployer_, v_deployer_; +}; + +TEST_F(VVerifierTest, TwoTxnNoConflict) { + Address transfer_receiver = get_random_address(); + LOG(ERROR)<<"start"; + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + + std::vector new_info; + std::vector rws_list; + for(int i = 0; i < resp.size();++i){ + rws_list.push_back(resp[i]->rws); + for(int j = 0; j < info.size();++j){ + if(info[j].commit_id == resp[i]->commit_id){ + new_info.push_back(info[j]); + } + } + } + + EXPECT_TRUE(verifier_->VerifyContract(new_info, rws_list)); + + } + LOG(ERROR)<<"done"; +} + +// a->b +TEST_F(VVerifierTest, TwoTxnConflict) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + + std::vector new_info; + std::vector rws_list; + for(int i = 0; i < resp.size();++i){ + rws_list.push_back(resp[i]->rws); + for(int j = 0; j < info.size();++j){ + if(info[j].commit_id == resp[i]->commit_id){ + new_info.push_back(info[j]); + } + } + } + + EXPECT_TRUE(verifier_->VerifyContract(new_info, rws_list)); + + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/xexecutor.cpp b/platform/consensus/ordering/fides/executor/manager/xexecutor.cpp new file mode 100644 index 000000000..daecb7360 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/xexecutor.cpp @@ -0,0 +1,287 @@ +/* + * 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 "service/contract/executor/paral_sm/streaming_dq_committer.h" + +#include +#include + +#include "service/contract/executor/paral_sm/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace paral_sm { + +XExecutor:: XExecutor( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + //LOG(ERROR)<<"init window:"<(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id<<" param:"<< + // request->GetContractExecuteInfo()->func_params.DebugString(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + request->SetResult(resp); + resp_queue_.Push(std::move(request)); + } + })); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void XExecutor::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +XExecutor::~XExecutor(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +/* +void XExecutor::CallBack(uint64_t commit_id){ + //LOG(ERROR)<<"call back:"< lk(mutex_); + cv_.notify_all(); + } + //LOG(ERROR)<<"call back done:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return id_ - first_id_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + //LOG(ERROR)<<"recv :"< q; + q.push(resp_commit_id); + while(!q.empty()){ + int64_t next_id = q.front(); + q.pop(); + + bool ret = controller_->Commit(next_id); + std::vector next_commit = controller_->GetRedo(); + //LOG(ERROR)<<"redo size:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + else { + q.push(new_next); + } + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<commit_id; + //LOG(ERROR)<<" !!!!! commit new resp:"<Commit(current_commit_id); + if(!ret){ + //LOG(ERROR)<<"redo size:"<GetRedo().size(); + if(controller_->GetRedo().size()){ + assert(controller_->GetRedo()[0] == current_commit_id); + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + else { + std::vector list = controller_->GetRedo(); + assert(list.empty()); + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<& requests) { + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + request_queue_.Push(std::make_unique(request)); + } + + return ; +} + +absl::StatusOr XExecutor::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/manager/xexecutor.h b/platform/consensus/ordering/fides/executor/manager/xexecutor.h new file mode 100644 index 000000000..42e3d523c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/manager/xexecutor.h @@ -0,0 +1,104 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/streaming_dq_controller.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/committer_context.h" +#include "service/contract/executor/manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace manager { + +class XExecutor : public ContractCommitter { + public: + XExecutor( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~XExecutor(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue> request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/BUILD b/platform/consensus/ordering/fides/executor/paral_sm/BUILD new file mode 100644 index 000000000..b048f4963 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/BUILD @@ -0,0 +1,304 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "utils", + hdrs = ["utils.h"], + deps = [ + "//third_party:evm_lib", + ], +) + +cc_library( + name = "address_manager", + srcs = ["address_manager.cpp"], + hdrs = ["address_manager.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_test( + name = "address_manager_test", + srcs = ["address_manager_test.cpp"], + deps = [ + ":address_manager", + "//common/test:test_main", + ], +) + +cc_library( + name = "data_storage", + srcs = ["data_storage.cpp"], + hdrs = ["data_storage.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_library( + name = "mock_data_storage", + hdrs = ["mock_data_storage.h"], + deps = [ + ":storage", + "//common/test:test" + ], +) + +cc_library( + name = "mock_d_storage", + hdrs = ["mock_d_storage.h"], + deps = [ + ":d_storage", + "//common/test:test" + ], +) + + +cc_library( + name = "d_storage", + srcs = ["d_storage.cpp"], + hdrs = ["d_storage.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + + +cc_library( + name = "leveldb", + srcs = ["leveldb.cpp"], + hdrs = ["leveldb.h"], + deps = [ + ":data_storage", + "//common:comm", + "//storage:res_leveldb" + ], +) + +cc_library( + name = "global_view", + srcs = ["global_view.cpp"], + hdrs = ["global_view.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + +cc_library( + name = "db_view", + srcs = ["db_view.cpp"], + hdrs = ["db_view.h"], + deps = [ + ":concurrency_controller", + ":xcontroller", + "//common:comm", + ], +) + +cc_library( + name = "executor_state", + srcs = ["executor_state.cpp"], + hdrs = ["executor_state.h"], + deps = [ + ":evm_state", + ":utils", + ":db_view", + "//common:comm", + ], +) + +cc_library( + name = "evm_state", + hdrs = ["evm_state.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_library( + name = "global_state", + srcs = ["global_state.cpp"], + hdrs = ["global_state.h"], + deps = [ + ":evm_state", + ":utils", + ":global_view", + "//common:comm", + ], +) + +cc_library( + name = "local_state", + srcs = ["local_state.cpp"], + hdrs = ["local_state.h"], + deps = [ + ":evm_state", + ":utils", + ":local_view", + "//common:comm", + ], +) + +cc_library( + name = "concurrency_controller", + srcs = ["concurrency_controller.cpp"], + hdrs = ["concurrency_controller.h"], + deps = [ + ":data_storage", + ], +) + +cc_library( + name = "xcontroller", + srcs = ["xcontroller.cpp"], + hdrs = ["xcontroller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "mock_e_controller", + hdrs = ["mock_e_controller.h"], + deps = [ + ":xcontroller", + "//common/test:test" + ], +) + +cc_library( + name = "contract_executor", + srcs = ["contract_executor.cpp"], + hdrs = ["contract_executor.h"], + deps = [ + ":evm_state", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "committer_context", + srcs = ["committer_context.cpp"], + hdrs = ["committer_context.h"], + deps = [ + ":contract_committer", + ], +) + +cc_library( + name = "contract_committer", + hdrs = ["contract_committer.h"], + deps = [ + ":contract_executor", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "local_view", + srcs = ["local_view.cpp"], + hdrs = ["local_view.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + + +cc_library( + name = "xexecutor", + srcs = ["xexecutor.cpp"], + hdrs = ["xexecutor.h"], + deps = [ + ":local_state", + ":contract_committer", + ":contract_executor", + ":committer_context", + ":xcontroller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "xexecutor_test", + srcs = ["xexecutor_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_d_storage", + ":mock_e_controller", + ":xexecutor", + "//common/test:test_main", + ], +) + + +cc_library( + name = "contract_deployer", + srcs = ["contract_deployer.cpp"], + hdrs = ["contract_deployer.h"], + deps = [ + ":concurrency_controller", + ":contract_committer", + ":global_state", + ":address_manager", + ], +) + +cc_test( + name = "contract_deployer_test", + srcs = ["contract_deployer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":test_committer", + ":contract_deployer", + "//common/test:test_main", + ], +) + +cc_library( + name = "contract_manager", + srcs = ["contract_manager.cpp"], + hdrs = ["contract_manager.h"], + deps = [ + ":contract_deployer", + ":xexecutor", + ":global_state", + ":address_manager", + ":utils", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "contract_manager_test", + srcs = ["contract_manager_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_manager", + "//common/test:test_main", + ], +) + diff --git a/platform/consensus/ordering/fides/executor/paral_sm/address_manager.cpp b/platform/consensus/ordering/fides/executor/paral_sm/address_manager.cpp new file mode 100644 index 000000000..328a33797 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/address_manager.cpp @@ -0,0 +1,76 @@ +/* + * 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 "service/contract/executor/x_manager/address_manager.h" + +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +Address AddressManager::CreateRandomAddress() { + std::vector raw(20); + std::generate(raw.begin(), raw.end(), []() { return rand(); }); + Address address = eevm::from_big_endian(raw.data(), raw.size()); + users_.insert(address); + return address; +} + +bool AddressManager::Exist(const Address& address) { + return users_.find(address) != users_.end(); +} + +Address AddressManager::CreateContractAddress(const Address& owner) { + return eevm::generate_address(owner, 0u); +} + +std::string AddressManager::AddressToHex(const Address& address) { + return eevm::to_hex_string(address); +} + +Address AddressManager::HexToAddress(const std::string& address) { + return eevm::to_uint256(address); +} + +uint256_t AddressManager::AddressToSHAKey(const Address& address) { + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + //code[63]=1; + //LOG(ERROR)<<"code size:"<(code.size()), h); + //LOG(ERROR)<<"get h:"< + +#include "service/contract/executor/x_manager/utils.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class AddressManager { + public: + AddressManager() {} + + // Create an address holding a 20 byte value + Address CreateRandomAddress(); + bool Exist(const Address& address); + + static Address CreateContractAddress(const Address& owner); + + static std::string AddressToHex(const Address& address); + static Address HexToAddress(const std::string& address); + + static uint256_t AddressToSHAKey(const Address& address); + + private: + std::set
users_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/address_manager_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/address_manager_test.cpp new file mode 100644 index 000000000..cb7d7454c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/address_manager_test.cpp @@ -0,0 +1,58 @@ +/* + * 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 "service/contract/executor/manager/address_manager.h" + +#include +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { +namespace { + +TEST(AddressManagerTest, CreateAddress) { + Address address = AddressManager().CreateRandomAddress(); + std::array raw; + eevm::to_big_endian(address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, address); +} + +TEST(AddressManagerTest, CreateContractAddress) { + Address address = AddressManager().CreateRandomAddress(); + + Address contract_address = AddressManager::CreateContractAddress(address); + + std::array raw; + eevm::to_big_endian(contract_address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, contract_address); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/committer_context.cpp b/platform/consensus/ordering/fides/executor/paral_sm/committer_context.cpp new file mode 100644 index 000000000..4a6a0d340 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/committer_context.cpp @@ -0,0 +1,65 @@ +/* + * 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 "service/contract/executor/paral_sm/committer_context.h" + +#include "glog/logging.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +ExecutionContext::ExecutionContext(const ContractExecuteInfo & info ) { + info_ = std::make_unique(info); +} + +const ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() const { + return info_.get(); +} + +void ExecutionContext::SetResult(std::unique_ptr result) { + result_ = std::move(result); +} + +bool ExecutionContext::IsRedo() { + return is_redo_; +} + +void ExecutionContext::SetRedo(){ + is_redo_++; +} + +int ExecutionContext::RedoTime() { + return is_redo_; +} + + +std::unique_ptr ExecutionContext::FetchResult() { + return std::move(result_); +} + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/committer_context.h b/platform/consensus/ordering/fides/executor/paral_sm/committer_context.h new file mode 100644 index 000000000..69fbbb6ad --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/committer_context.h @@ -0,0 +1,54 @@ +/* + * 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 "service/contract/executor/paral_sm/contract_committer.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +class ExecutionContext { +public: + ExecutionContext(const ContractExecuteInfo & info ) ; + const ContractExecuteInfo * GetContractExecuteInfo() const; + + void SetRedo(); + bool IsRedo(); + int RedoTime(); + + void SetResult(std::unique_ptr result); + std::unique_ptr FetchResult(); + + private: + int is_redo_ = 0; + std::unique_ptr result_; + std::unique_ptr info_; +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.cpp b/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.cpp new file mode 100644 index 000000000..accac4f63 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.cpp @@ -0,0 +1,17 @@ +#include "service/contract/executor/paral_sm/concurrency_controller.h" + +#include + +namespace resdb { +namespace contract { + +ConcurrencyController::ConcurrencyController(DataStorage * storage) : storage_(storage){} + +const DataStorage * ConcurrencyController::GetStorage() const { + return storage_; +} + + + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.h b/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.h new file mode 100644 index 000000000..c1fa7df12 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/concurrency_controller.h @@ -0,0 +1,46 @@ +#pragma once + +#include "service/contract/executor/paral_sm/data_storage.h" + +#include +#include + +namespace resdb { +namespace contract { + +enum State{ + LOAD = 0, + STORE = 1, + REMOVE = 2, +}; + +struct Data{ + State state; + uint256_t data; + int64_t version; + uint256_t old_data; + int commit_version; + Data(){} + Data(const State& state):state(state){} + Data(const State& state, const uint256_t& data, int64_t version = 0) + :state(state), data(data), version(version){} + Data(const State& state, const uint256_t& data, int64_t version, const uint256_t& old_data) + :state(state), data(data), version(version), old_data(old_data){} +}; + +class ConcurrencyController { + public: + ConcurrencyController(DataStorage * storage); + + typedef std::map> ModifyMap; + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_) = 0; + + const DataStorage * GetStorage() const; + + protected: + DataStorage * storage_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_committer.h b/platform/consensus/ordering/fides/executor/paral_sm/contract_committer.h new file mode 100644 index 000000000..3c0d3085f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_committer.h @@ -0,0 +1,76 @@ +/* + * 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 "service/contract/executor/paral_sm/contract_executor.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "eEVM/address.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +struct ContractExecuteInfo { + eevm::Address caller_address; + eevm::Address contract_address; + std::string func_addr; + Params func_params; + int64_t commit_id; + uint64_t user_id; + ContractExecuteInfo(){} + ContractExecuteInfo( + eevm::Address caller_address, + eevm::Address contract_address, + std::string func_addr, + Params func_params, + int64_t commit_id): caller_address(caller_address), contract_address(contract_address), func_addr(func_addr), func_params(func_params), commit_id(commit_id){} +}; + +class ContractCommitter { + public: + ContractCommitter() = default; + virtual ~ContractCommitter() = default; + + virtual std::vector> ExecContract(std::vector& request) = 0; + + virtual void AsyncExecContract(std::vector& request){}; + + virtual absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) = 0; + + virtual void SetExecuteCallBack(std::function)> ){}; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.cpp new file mode 100644 index 000000000..0ad900050 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.cpp @@ -0,0 +1,130 @@ +/* + * 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 "service/contract/executor/x_manager/contract_deployer.h" + +#include "service/contract/executor/x_manager/address_manager.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); + LOG(ERROR)<<"add arg:"<& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +} + +ContractDeployer::ContractDeployer(ContractCommitter * committer, GlobalState * gs) + : committer_(committer), gs_(gs){} + +std::string ContractDeployer::GetFuncAddress(const Address& contract_address, + const std::string& func_name) { + return func_address_[contract_address][func_name]; +} + +void ContractDeployer::SetFuncAddress(const Address& contract_address, + const FuncInfo& func) { + func_address_[contract_address][func.func_name()] = func.hash(); +} + +Address ContractDeployer::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info) { + const auto contract_address = + AddressManager::CreateContractAddress(owner_address); + + auto contract_constructor = eevm::to_bytes(deploy_info.contract_bin()); + for (const std::string& param : deploy_info.init_param()) { + AppendArgToInput(contract_constructor, param); + } + + try { + auto contract = gs_->create(contract_address, 0u, contract_constructor); + absl::StatusOr result = committer_->ExecContract( + owner_address, contract_address, + "", + {}, gs_); + + if(result.ok()){ + // set the initialized class context code. + contract.acc.set_code(eevm::to_bytes(*result)); + + for (const auto& info : deploy_info.func_info()) { + SetFuncAddress(contract_address, info); + } + return contract.acc.get_address(); + } else { + gs_->remove(contract_address); + return 0; + } + } catch (...) { + LOG(ERROR) << "Deploy throw expection"; + return 0; + } +} + +Address ContractDeployer::DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params){ + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json["bin"]); + + for (auto& func : contract_json["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + for(const uint256_t& param : init_params){ + deploy_info.add_init_param(U256ToString(param)); + } + + return DeployContract(owner_address, deploy_info); +} + +absl::StatusOr ContractDeployer::GetContract( + const Address& address) { + if (!gs_->Exists(address)) { + return absl::InvalidArgumentError("Contract not exist."); + } + + return gs_->get(address); +} + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.h b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.h new file mode 100644 index 000000000..cb75643b3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer.h @@ -0,0 +1,62 @@ +/* + * 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 "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ContractDeployer { + public: + ContractDeployer(ContractCommitter * committer, GlobalState * gs); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + + Address DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params); + + absl::StatusOr GetContract(const Address& address); + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + + private: + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + ContractCommitter* committer_; + GlobalState* gs_; + std::map> func_address_; +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer_test.cpp new file mode 100644 index 000000000..41be4ae2c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_deployer_test.cpp @@ -0,0 +1,124 @@ +/* + * 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 "service/contract/executor/manager/contract_deployer.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/test_committer.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +class ContractDeployerTest : public Test { + public: + ContractDeployerTest() : owner_address_(get_random_address()) { + + storage_ = std::make_unique(); + gs_ = std::make_unique(storage_.get()); + execotor_ = std::make_unique(storage_.get(), gs_.get()); + + + + LOG(ERROR)<<"owner:"< storage_; + std::unique_ptr gs_; + std::unique_ptrexecotor_; +}; + +TEST_F(ContractDeployerTest, NoContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + auto account = deployer.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + deployer.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContractFromJson) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + Address contract_address = + deployer.DeployContract(owner_address_, contract_json_, {1000}); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.cpp new file mode 100644 index 000000000..1809b6062 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.cpp @@ -0,0 +1,103 @@ +/* + * 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 "service/contract/executor/paral_sm/contract_executor.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); +} + +void AppendArgToInput(std::vector& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +absl::StatusOr ContractExecutor::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + + std::vector inputs; + if(!func_addr.empty()){ + inputs = eevm::to_bytes(func_addr); + } + for (const std::string& param : func_param.param()) { + AppendArgToInput(inputs, param); + } + + auto result = Execute(caller_address, contract_address, inputs, state); + + if (result.ok()) { + return eevm::to_hex_string(*result); + } + else { + LOG(ERROR)<<"execute fail:"<> ContractExecutor::Execute( + const Address& caller_address, const Address& contract_address, + const std::vector& input, + EVMState * state) { + // Ignore any logs produced by this transaction + eevm::NullLogHandler ignore; + eevm::Transaction tx(caller_address, ignore); + + + // Record a trace to aid debugging + eevm::Trace tr; + eevm::Processor p(*state); + + // Run the transaction + try { + const auto exec_result = + p.run(tx, caller_address, state->get(contract_address), input, 0u, &tr); + + if (exec_result.er != eevm::ExitReason::returned) { + // Print the trace if nothing was returned + if (exec_result.er == eevm::ExitReason::threw) { + return absl::InternalError( + fmt::format("Execution error: {}", exec_result.exmsg)); + } + return absl::InternalError("Deployment did not return"); + } + return exec_result.output; + } catch (...) { + return absl::InternalError(fmt::format("Execution error:")); + } +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.h b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.h new file mode 100644 index 000000000..ab1484642 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor.h @@ -0,0 +1,68 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/paral_sm/utils.h" +#include "service/contract/executor/paral_sm/evm_state.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +struct ExecuteResp { + int ret; + absl::Status state; + int64_t commit_id; + Address contract_address; + std::string result; + int retry_time = 0; + uint64_t user_id = 0; +}; + +class ContractExecutor { + public: + ContractExecutor() = default; + + ~ContractExecutor() = default; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + absl::StatusOr> Execute( + const Address& owner_address, const Address& contract_address, + const std::vector& func_params, EVMState * state); +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_executor_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor_test.cpp new file mode 100644 index 000000000..3c14e08c5 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_executor_test.cpp @@ -0,0 +1,367 @@ +/* + * 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 "service/contract/executor/manager/multi_contract_executor.h" +#include "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +uint256_t GetAddressHash(uint256_t address){ + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + code[63]=1; + + uint8_t h[32]; + eevm::keccak_256(code.data(), static_cast(code.size()), h); + return eevm::from_big_endian(h, sizeof(h)); +} + + +class MultiContractExecutorTest : public Test { + public: + MultiContractExecutorTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + LOG(ERROR)<<"test dir:"< storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + LOG(ERROR)<<"owner address:"<Load(owner_key).first, 1000); + } + +/* + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 0); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 300); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + */ +} +/* + +TEST_F(MultiContractExecutorTest, ExecMultiContractNoConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + info.push_back(ContractExecuteInfo(transfer_receiver, contract_address, "", func_params, 0)); + } + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 200); + EXPECT_EQ(storage_ptr->Load(transfer3_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} + +TEST_F(MultiContractExecutorTest, ExecMultiContractHaveConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 800); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} +*/ + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.cpp new file mode 100644 index 000000000..95ab216c4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.cpp @@ -0,0 +1,114 @@ +/* + * 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 "service/contract/executor/x_manager/contract_manager.h" + +#include "service/contract/executor/x_manager/address_manager.h" +#include "service/contract/executor/x_manager/streaming_e_committer.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +ContractManager::ContractManager(std::unique_ptr storage, + int worker_num, Options op){ + storage_ = std::move(storage); + gs_ = std::make_unique(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get(), 500, nullptr, worker_num); + + deployer_ = std::make_unique(committer_.get(), gs_.get()); +} + +void ContractManager::SetExecuteCallBack(std::function resp)> func) { + committer_->SetExecuteCallBack(std::move(func)); +} + +Address ContractManager::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info) { + return deployer_->DeployContract(owner_address, deploy_info); +} + +absl::StatusOr ContractManager::GetContract(const Address& address) { + return deployer_->GetContract(address); +} + +std::vector> ContractManager::ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); +} + +void ContractManager::AsyncExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + committer_->AsyncExecContract(execute_info); +} + + +absl::StatusOr ContractManager::ExecContract( + const Address& caller_address, const Address& contract_address, + const Params& func_param) { + std::string func_addr = + deployer_->GetFuncAddress(contract_address, func_param.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << func_param.func_name(); + return absl::InvalidArgumentError("Func not exist."); + } + + absl::StatusOr result = committer_->ExecContract( + caller_address, contract_address, + func_addr, + func_param, gs_.get()); + if(result.ok()){ + return *result; + } + return result.status(); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.h b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.h new file mode 100644 index 000000000..bc81a2e32 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager.h @@ -0,0 +1,78 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_deployer.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ContractManager { + public: + enum Options { + Streaming = 1, + }; + ContractManager(std::unique_ptr storage, + int worker_num = 2, Options op = Streaming ); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + + absl::StatusOr GetContract(const Address& address); + + absl::StatusOr ExecContract(const Address& caller_address, + const Address& contract_address, + const Params& func_param); + + std::vector> ExecContract(std::vector& execute_info); + + void AsyncExecContract(std::vector& execute_info); + + void SetExecuteCallBack(std::function resp)> func); + + private: + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptr committer_; + std::unique_ptr deployer_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/contract_manager_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager_test.cpp new file mode 100644 index 000000000..759a24666 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/contract_manager_test.cpp @@ -0,0 +1,259 @@ +/* + * 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 "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class ContractManagerTest : public Test { + public: + ContractManagerTest() : owner_address_(get_random_address()) { + LOG(ERROR)<<"owner:"<()); + auto account = manager.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractManagerTest, DeployContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractManagerTest, InitContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + LOG(ERROR)<<" deploy done:"<()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1000); + } + + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + // receiver 400 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 400); + } + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } +} + +TEST_F(ContractManagerTest, NoFunc) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf()"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_FALSE(result.ok()); + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/d_storage.cpp b/platform/consensus/ordering/fides/executor/paral_sm/d_storage.cpp new file mode 100644 index 000000000..56f298c61 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/d_storage.cpp @@ -0,0 +1,151 @@ +#include "service/contract/executor/paral_sm/d_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + return v+1; +} + +std::pair InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return std::make_pair(0,0); + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + return 0; + } + return it->second.second; +} + +} + +int64_t D_Storage::StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local) { +//LOG(ERROR)<<"storage key:"< D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"<second.second)<<" key:"< +#include + +#include "service/contract/executor/paral_sm/data_storage.h" + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class D_Storage : public DataStorage { + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual int64_t StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_; + mutable std::shared_mutex mutex_; + + std::map > g_s_; + mutable std::shared_mutex g_mutex_; + +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/data_storage.cpp b/platform/consensus/ordering/fides/executor/paral_sm/data_storage.cpp new file mode 100644 index 000000000..407f9b655 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/data_storage.cpp @@ -0,0 +1,51 @@ +#include "service/contract/executor/paral_sm/data_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +int64_t DataStorage::Store(const uint256_t& key, const uint256_t& value, bool) { + std::unique_lock lock(mutex_); + //LOG(ERROR)<<"store key:"< DataStorage::Load(const uint256_t& key, bool) const { + std::shared_lock lock(mutex_); + //LOG(ERROR)<<"load key:"<second; +} + +bool DataStorage::Remove(const uint256_t& key, bool) { + std::unique_lock lock(mutex_); + auto e = s.find(key); + if (e == s.end()) + return false; + s.erase(e); + return true; +} + +bool DataStorage::Exist(const uint256_t& key, bool) const { + std::shared_lock lock(mutex_); + return s.find(key) != s.end(); +} + +int64_t DataStorage::GetVersion(const uint256_t& key, bool) const{ + LOG(ERROR)<<"?????"; + std::shared_lock lock(mutex_); + auto it = s.find(key); + if( it == s.end()){ + return 0; + } + return it->second.second; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/data_storage.h b/platform/consensus/ordering/fides/executor/paral_sm/data_storage.h new file mode 100644 index 000000000..158e54909 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/data_storage.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class DataStorage { + +public: + DataStorage() = default; + virtual ~DataStorage() = default; + + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local = false); + virtual std::pair Load(const uint256_t& key, bool is_local_view = false) const; + virtual bool Remove(const uint256_t& key, bool is_local = false); + virtual bool Exist(const uint256_t& key, bool is_local = false) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local = false) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local = false) {} + virtual void Flush(){}; + +protected: + std::map > s; + mutable std::shared_mutex mutex_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/db_view.cpp b/platform/consensus/ordering/fides/executor/paral_sm/db_view.cpp new file mode 100644 index 000000000..a551251d0 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/db_view.cpp @@ -0,0 +1,40 @@ +#include "service/contract/executor/paral_sm/db_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { +namespace paral_sm { + +DBView::DBView(ConcurrencyController * controller, int64_t commit_id, int version) + :controller_(static_cast(controller)), + commit_id_(commit_id), version_(version){ + } + +void DBView::store(const uint256_t& key, const uint256_t& value) { + controller_->Store(commit_id_, key, value, version_); +} + +uint256_t DBView::load(const uint256_t& key) { + return controller_->Load(commit_id_, key, version_); +} + +bool DBView::remove(const uint256_t& key) { + //LOG(ERROR)<<"remove key:"<Remove(commit_id_, key, version_); +} + +/* +void DBView::Flesh(int64_t commit_id) { + commit_id_ = commit_id; + //LOG(ERROR)<<"commit push:"<PushCommit(commit_id, local_changes_); + local_changes_.clear(); +} +*/ + + +}} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/db_view.h b/platform/consensus/ordering/fides/executor/paral_sm/db_view.h new file mode 100644 index 000000000..203ab23c6 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/db_view.h @@ -0,0 +1,41 @@ +#pragma once + +#include + +#include "service/contract/executor/paral_sm/concurrency_controller.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +class DBView : public eevm::Storage { + +public: + DBView(ConcurrencyController * controller, int64_t commit_id, int version); + virtual ~DBView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + + // for 2PL, once it is done, all the commit will be pushed to + // the controller to judge if it can be committed. + // During the flesh, all the changes will be removed. + void Flesh(int64_t commit_id) {} + // Commit the changes. If there is a conflict, return false. + // Make sure all other committers have pushed their changes before calling Commit. + //bool Commit(); + // Remove all the changes. + //void Abort(); + +private: + ingEController * controller_; + int64_t commit_id_; + int version_; + std::map> local_changes_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/evm_state.h b/platform/consensus/ordering/fides/executor/paral_sm/evm_state.h new file mode 100644 index 000000000..ccd0d9500 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/evm_state.h @@ -0,0 +1,23 @@ +#pragma once + +#include "eEVM/globalstate.h" + +namespace resdb { +namespace contract { + +class EVMState : public eevm::GlobalState { +public: + EVMState() = default; + virtual ~EVMState() = default; + +protected: + const eevm::Block& get_current_block() override { return block_; } + uint256_t get_block_hash(uint8_t offset) override { return 0; } + +private: + // Unused. + eevm::Block block_; +}; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/executor_state.h b/platform/consensus/ordering/fides/executor/paral_sm/executor_state.h new file mode 100644 index 000000000..222682c70 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/executor_state.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/paral_sm/db_view.h" +#include "service/contract/executor/paral_sm/concurrency_controller.h" +#include "service/contract/executor/paral_sm/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + + class ExecutorState : public EVMState { + public: + using StateEntry = std::pair; + + public: + ExecutorState(ConcurrencyController * controller, int64_t commit_id); + virtual ~ExecutorState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Flesh the local view to the controller with a commit id. + // Once all the contracts have fleshed their changes, they should call commit. + // Return false if contract not exists. + bool Flesh(const eevm::Address& addr, int commit_id); + // Commit the changes using the commit id from the flesh. + //bool Commit(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + void Set(const eevm::SimpleAccount& acc, int64_t commit_id, int version); + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + ConcurrencyController * controller_; + }; + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/global_state.cpp b/platform/consensus/ordering/fides/executor/paral_sm/global_state.cpp new file mode 100644 index 000000000..34db1839d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/global_state.cpp @@ -0,0 +1,52 @@ +#include "service/contract/executor/paral_sm/global_state.h" + +#include + +namespace resdb { +namespace contract { +namespace paral_sm { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + GlobalState::GlobalState(DataStorage * storage) : storage_(storage) { + } + + bool GlobalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void GlobalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState GlobalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState GlobalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), GlobalView(storage_)}); + + return get(addr); + } + + const eevm::SimpleAccount& GlobalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void GlobalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/global_state.h b/platform/consensus/ordering/fides/executor/paral_sm/global_state.h new file mode 100644 index 000000000..dca091e85 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/global_state.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/paral_sm/global_view.h" +#include "service/contract/executor/paral_sm/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + + class GlobalState : public EVMState{ + public: + using StateEntry = std::pair; + + public: + GlobalState(DataStorage* storage); + virtual ~GlobalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + DataStorage* storage_; + }; + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/global_view.cpp b/platform/consensus/ordering/fides/executor/paral_sm/global_view.cpp new file mode 100644 index 000000000..f9ce4837a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/global_view.cpp @@ -0,0 +1,27 @@ +#include "service/contract/executor/paral_sm/global_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { +namespace paral_sm { + +GlobalView::GlobalView(DataStorage* storage) :storage_(storage){ } + +void GlobalView::store(const uint256_t& key, const uint256_t& value) { + storage_->Store(key, value); +} + +uint256_t GlobalView::load(const uint256_t& key) { + return storage_->Load(key).first; +} + +bool GlobalView::remove(const uint256_t& key) { + return storage_->Remove(key); +} + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/global_view.h b/platform/consensus/ordering/fides/executor/paral_sm/global_view.h new file mode 100644 index 000000000..a57a4b535 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/global_view.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "service/contract/executor/paral_sm/data_storage.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +class GlobalView : public eevm::Storage { + +public: + GlobalView(DataStorage* storage); + virtual ~GlobalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + +private: + DataStorage * storage_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/leveldb.cpp b/platform/consensus/ordering/fides/executor/paral_sm/leveldb.cpp new file mode 100644 index 000000000..b540aed45 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/leveldb.cpp @@ -0,0 +1,30 @@ +#include "service/contract/executor/manager/leveldb.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +LevelDB::LevelDB(){ + db_ = std::make_unique("./"); + db_->SetBatchSize(10000); +} + +void LevelDB::Flush(){ + //LOG(ERROR)<<"flush"; + for(const auto& it : s){ + std::string addr = eevm::to_hex_string(it.first); + std::string value = eevm::to_hex_string(it.second.first); + //LOG(ERROR)<<"addr:"<SetValue(addr, std::string(buf, value.size()+sizeof(it.second.second))); + delete buf; + } +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/leveldb.h b/platform/consensus/ordering/fides/executor/paral_sm/leveldb.h new file mode 100644 index 000000000..e76ca274f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/leveldb.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDB : public DataStorage { + +public: + LevelDB(); + virtual ~LevelDB() = default; + + virtual void Flush(); + +private: + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/local_state.cpp b/platform/consensus/ordering/fides/executor/paral_sm/local_state.cpp new file mode 100644 index 000000000..6dc0014e3 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/local_state.cpp @@ -0,0 +1,73 @@ +#include "service/contract/executor/paral_sm/local_state.h" + +#include + +namespace resdb { +namespace contract { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + LocalState::LocalState(ConcurrencyController * controller) : controller_(controller) { + } + + bool LocalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void LocalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState LocalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState LocalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), LocalView(controller_, 0)}); + return get(addr); + } + + const eevm::SimpleAccount& LocalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void LocalState::Set(const eevm::SimpleAccount& acc, int64_t commit_id) { + Insert({acc, LocalView(controller_, commit_id)}); + } + + void LocalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } + + bool LocalState::Flesh(const Address& addr, int commit_id) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + acc->second.second.Flesh(commit_id); + return true; + } + return false; + } + +/* + bool LocalState::Commit(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + return acc->second.second.Commit(); + } + return false; + } + */ + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/local_state.h b/platform/consensus/ordering/fides/executor/paral_sm/local_state.h new file mode 100644 index 000000000..90a1d02e5 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/local_state.h @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/paral_sm/local_view.h" +#include "service/contract/executor/paral_sm/concurrency_controller.h" +#include "service/contract/executor/paral_sm/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { + + class LocalState : public EVMState { + public: + using StateEntry = std::pair; + + public: + LocalState(ConcurrencyController * controller); + virtual ~LocalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Flesh the local view to the controller with a commit id. + // Once all the contracts have fleshed their changes, they should call commit. + // Return false if contract not exists. + bool Flesh(const eevm::Address& addr, int commit_id); + // Commit the changes using the commit id from the flesh. + //bool Commit(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + void Set(const eevm::SimpleAccount& acc, int64_t commit_id); + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + ConcurrencyController * controller_; + }; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/local_view.cpp b/platform/consensus/ordering/fides/executor/paral_sm/local_view.cpp new file mode 100644 index 000000000..70a828885 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/local_view.cpp @@ -0,0 +1,50 @@ +#include "service/contract/executor/paral_sm/local_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { + +LocalView::LocalView(ConcurrencyController * controller, int64_t commit_id) + :controller_(controller), + commit_id_(commit_id){ } + +void LocalView::store(const uint256_t& key, const uint256_t& value) { + //LOG(ERROR)<<"========= store key:"< load_value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(STORE, value, load_value.second, load_value.first)); + } + else { + local_changes_[key].push_back(Data(STORE, value)); + } +} + +uint256_t LocalView::load(const uint256_t& key) { + //LOG(ERROR)<<"load key:"< value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(LOAD, value.first, value.second)); + return value.first; + } + return it->second.back().data; +} + +bool LocalView::remove(const uint256_t& key) { + local_changes_[key].push_back(Data(REMOVE)); + return true; +} + +void LocalView::Flesh(int64_t commit_id) { + commit_id_ = commit_id; + //LOG(ERROR)<<"commit push:"<PushCommit(commit_id, local_changes_); + local_changes_.clear(); +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/local_view.h b/platform/consensus/ordering/fides/executor/paral_sm/local_view.h new file mode 100644 index 000000000..52af688a8 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/local_view.h @@ -0,0 +1,38 @@ +#pragma once + +#include + +#include "service/contract/executor/paral_sm/concurrency_controller.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { + +class LocalView : public eevm::Storage { + +public: + LocalView(ConcurrencyController * controller, int64_t commit_id); + virtual ~LocalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + + // for 2PL, once it is done, all the commit will be pushed to + // the controller to judge if it can be committed. + // During the flesh, all the changes will be removed. + void Flesh(int64_t commit_id); + // Commit the changes. If there is a conflict, return false. + // Make sure all other committers have pushed their changes before calling Commit. + //bool Commit(); + // Remove all the changes. + //void Abort(); + +private: + ConcurrencyController * controller_; + int64_t commit_id_; + std::map> local_changes_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/local_view_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/local_view_test.cpp new file mode 100644 index 000000000..f05672a9f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/local_view_test.cpp @@ -0,0 +1,125 @@ +/* + * 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 "service/contract/executor/manager/local_view.h" +#include "service/contract/executor/manager/two_phase_controller.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +TEST(LocalViewTest, ViewChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); +} + +TEST(LocalViewTest, CommitChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + view.Flesh(0); + EXPECT_TRUE(controller.Commit(0)); + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + +TEST(LocalViewTest, CommitConflict) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view1(&controller, 0); + LocalView view2(&controller, 1); + + EXPECT_EQ(view1.load(address1), 2000) ; + view1.store(address1, 3000); + EXPECT_EQ(view1.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + EXPECT_EQ(view2.load(address1), 2000) ; + view2.store(address1, 4000); + EXPECT_EQ(view2.load(address1), 4000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + view1.Flesh(0); + view2.Flesh(1); + + EXPECT_FALSE(controller.Commit(1)); + EXPECT_TRUE(controller.Commit(0)); + + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/mock_d_storage.h b/platform/consensus/ordering/fides/executor/paral_sm/mock_d_storage.h new file mode 100644 index 000000000..976bf9e7f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/mock_d_storage.h @@ -0,0 +1,50 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/x_manager/d_storage.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class MockDStorage : public D_Storage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value, bool), (override)); + MOCK_METHOD(int64_t, StoreWithVersion, (const uint256_t& key, const uint256_t& value, int version, bool), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&, bool), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(void, Reset, (const uint256_t&, const uint256_t&, int64_t, bool), (override)); +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/mock_data_storage.h b/platform/consensus/ordering/fides/executor/paral_sm/mock_data_storage.h new file mode 100644 index 000000000..f438afabc --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/mock_data_storage.h @@ -0,0 +1,46 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/manager/data_storage.h" + +namespace resdb { +namespace contract { + +class MockStorage : public DataStorage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&), (const, override)); +}; + +} +} // namespace resdb diff --git a/chain/storage/txn_memory_db.cpp b/platform/consensus/ordering/fides/executor/paral_sm/mock_e_controller.h similarity index 69% rename from chain/storage/txn_memory_db.cpp rename to platform/consensus/ordering/fides/executor/paral_sm/mock_e_controller.h index f16445c0e..afdfc58bd 100644 --- a/chain/storage/txn_memory_db.cpp +++ b/platform/consensus/ordering/fides/executor/paral_sm/mock_e_controller.h @@ -23,28 +23,23 @@ * */ -#include "chain/storage/txn_memory_db.h" +#pragma once -#include +#include "gmock/gmock.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" namespace resdb { +namespace contract { +namespace x_manager { -TxnMemoryDB::TxnMemoryDB() : max_seq_(0) {} +class MockEController : public StreamingEController { + public: + MockEController(DataStorage* storage, int window):StreamingEController(storage, window){} -Request* TxnMemoryDB::Get(uint64_t seq) { - std::unique_lock lk(mutex_); - if (data_.find(seq) == data_.end()) { - return nullptr; - } - return data_[seq].get(); -} + MOCK_METHOD(void, Store, (const int64_t, const uint256_t& key, const uint256_t& value, int), (override)); + MOCK_METHOD(uint256_t, Load, (const int64_t, const uint256_t&, int), (override)); +}; -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/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.cpp b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.cpp new file mode 100644 index 000000000..9e247242a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.cpp @@ -0,0 +1,314 @@ +/* + * 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 "service/contract/executor/x_manager/streaming_e_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace x_manager { + +StreamingECommitter:: StreamingECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + //LOG(ERROR)<<"init window:"<(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id<<" param:"<< + // request->GetContractExecuteInfo()->func_params.DebugString(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + //local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + // request->GetContractExecuteInfo()->commit_id); + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + resp_queue_.Push(std::move(resp)); + } + })); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void StreamingECommitter::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +StreamingECommitter::~StreamingECommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +void StreamingECommitter::SetController(std::unique_ptr controller) { + controller_ = std::move(controller); +} + +void StreamingECommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id%window_size_] = std::move(context); +} + +void StreamingECommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* StreamingECommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id%window_size_].get(); +} + +void StreamingECommitter::CallBack(uint64_t commit_id){ + //LOG(ERROR)<<"call back:"< lk(mutex_); + cv_.notify_all(); + } + //LOG(ERROR)<<"call back done:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return id_ - first_id_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + //LOG(ERROR)<<"recv :"< q; + q.push(resp_commit_id); + while(!q.empty()){ + int64_t next_id = q.front(); + q.pop(); + + bool ret = controller_->Commit(next_id); + std::vector next_commit = controller_->GetRedo(); + //LOG(ERROR)<<"redo size:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + else { + q.push(new_next); + } + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<commit_id; + //LOG(ERROR)<<" !!!!! commit new resp:"<Commit(current_commit_id); + //LOG(ERROR)<<"commit id:"<GetRedo().size(); + if(controller_->GetRedo().size()){ + assert(controller_->GetRedo()[0] == current_commit_id); + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + else { + std::vector list = controller_->GetRedo(); + assert(list.empty()); + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<& requests) { + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + int cur_idx = id_%window_size_; + assert(id_>=last_id_); + + request.commit_id = id_++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + + //LOG(ERROR)<<"execute:"<(context_ptr)); + } + + return ; +} + +absl::StatusOr StreamingECommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.h b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.h new file mode 100644 index 000000000..eaed943b4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class StreamingECommitter : public ContractCommitter { + public: + StreamingECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingECommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer_test.cpp b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer_test.cpp new file mode 100644 index 000000000..b0ab8c340 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/streaming_e_committer_test.cpp @@ -0,0 +1,889 @@ +/* + * 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 "service/contract/executor/x_manager/streaming_e_committer.h" + +#include + +#include "service/contract/executor/x_manager/mock_d_storage.h" +#include "service/contract/executor/x_manager/mock_e_controller.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_deployer.h" +#include "service/contract/executor/x_manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = "/home/ubuntu/nexres//service/contract/executor/manager/"; +//const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + // std::string(getenv("TEST_WORKSPACE")) + + // "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class StreamingECommitterTest : public Test { + public: + StreamingECommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/kv.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["kv.sol:KV"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load:"<(storage_.get()); + + auto controller = std::make_unique(storage_.get(), 10); + controller_ = controller.get(); + + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + return controller_->LoadInternal(commit_id, address, version); + })); + + EXPECT_CALL(*controller_, Store).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version) { + return controller_->StoreInternal(commit_id, key, value, version); + })); + + + committer_ = std::make_unique(storage_.get(), gs_.get(), 10); + committer_->SetController(std::move(controller)); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + void ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + committer_->AsyncExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + MockEController* controller_; + std::map> data_, g_data_; + std::unique_ptr deployer_; +}; + +TEST_F(StreamingECommitterTest, ExecContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done.set_value(true); + }); + + { + ExecContract(info); + } + done_future.get(); + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + //LOG(ERROR)<<"owner key:"< done; + std::future done_future = done.get_future(); + std::vector info; + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + assert(resp != nullptr); + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); +} + +TEST_F(StreamingECommitterTest, ExecConflictContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::set ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + ids.insert(commit_id); + return controller_->LoadInternal(commit_id, address, version); + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); + +} + +TEST_F(StreamingECommitterTest, ExecConflictContractAhead2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(transfer_receiver3)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + EXPECT_EQ(ids[2], 1); + EXPECT_EQ(ids[1], 0); + EXPECT_EQ(ids[3], 0); + + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); + +} + +TEST_F(StreamingECommitterTest, ExecConflictContractAhead) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 300); + EXPECT_EQ(data_[recer3].first, 500); +} + +TEST_F(StreamingECommitterTest, ExecConflictPreCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 800); +} + +TEST_F(StreamingECommitterTest, ExecConflictPreCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 900); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 300); +} + +TEST_F(StreamingECommitterTest, ExecConflictCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + if(data_.find(address) == data_.end()){ + data_[address] = g_data_[address]; + } + } + if(is_local){ + load_time[address]++; + } + auto ret = is_local? data_[address]: g_data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + if(is_local){ + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + } + else { + int v = g_data_[key].second; + g_data_[key] = std::make_pair(value, v+1); + } + return 1; + })); + + EXPECT_CALL(*storage_, Reset).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value, int64_t version, bool is_local) { + LOG(ERROR)<<"reset key:"< done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 800); + EXPECT_EQ(data_[recer2].first, 300); + +} + +// rollback two columns +TEST_F(StreamingECommitterTest, ExecConflictCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::set pre_ids; + controller_->SetPrecommitCallback([&](int64_t id){ + pre_ids.insert(id); + }); + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==2 && version>0){ + while(pre_ids.size()<5){ + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + Address transfer_receiver4 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"!!!!! get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==6){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + LOG(ERROR)<<"recr:"< +#include +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + return v%128; + } + + + void AppendRecord(const uint256_t& address, + int64_t commit_id, StreamingEController::CommitList * commit_list) { + int hash_idx = GetHashKey(address); + auto& commit_set = commit_list[hash_idx][address]; + if(!commit_set.empty()&& commit_set.back() > commit_id){ + //LOG(ERROR)<<"append to old data:"<(storage)){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< precommit_callback) { + precommit_callback_ = std::move(precommit_callback); +} + +void StreamingEController::Clear(){ + last_commit_id_=1; + current_commit_id_=1; + last_pre_commit_id_=0; + + assert(window_size_+1<=1024); + //is_redo_.resize(window_size_+1); + //is_done_.resize(window_size_+1); + changes_list_.resize(window_size_+1); + rechanges_list_.resize(window_size_+1); + + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + rechanges_list_[i].clear(); + is_redo_[i] = 0; + //is_done_[i] = false; + } + + for(int i = 0; i < 1024; ++i){ + commit_list_[i].clear(); + pre_commit_list_[i].clear(); + } +} + +std::vector& StreamingEController::GetRedo(){ + return redo_; +} + +std::vector& StreamingEController::GetDone(){ + return done_; +} + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + + +// ======================================================== +void StreamingEController::StoreInternal(const int64_t commit_id, const uint256_t& address, const uint256_t& value, int version) { + Data data = Data(STORE, value); + AppendPreRecord(address, commit_id, data, version); + //LOG(ERROR)<<"store data commit id:"< lk(mutex_[hash_idx]); + std::unique_lock lock(mutex_[hash_idx]); + + auto& commit_set = pre_commit_list_[hash_idx][address]; + auto it = commit_set.find(commit_id); + if(it == commit_set.end() || it->second.commit_version != version){ + it = commit_set.insert(std::make_pair(commit_id,Data())).first; + if(it == commit_set.begin()){ + auto ret = storage_->Load(address, true); + data.old_data = ret.first; + data.version = ret.second; + //LOG(ERROR)<<"get from db:"<second.data; + data.version = pt->second.version; + //LOG(ERROR)<<"get from pre:"<first<<" ver:"<second.version; + } + } + else { + data.old_data = it->second.data; + data.version= it->second.version; + //LOG(ERROR)<<"get from self:"<first<<" ver:"<second.version<<" commit ver:"<second.commit_version; + } + + if(data.state == LOAD){ + data.data = data.old_data; + } + else { + data.version += 1; + } + //LOG(ERROR)<<"!!!!! append id:"<second = data; + } + + int idx = commit_id&window_size_; + auto& change_set = changes_list_[idx][address]; + if(change_set.empty()){ + change_set.push_back(data); + } + else { + int old_version = change_set.front().commit_version; + if(old_version != version){ + for(auto it : changes_list_[idx]){ + if(rechanges_list_[idx].find(it.first) == rechanges_list_[idx].end()){ + rechanges_list_[idx][it.first].push_back(it.second.front()); + } + } + changes_list_[idx].clear(); + } + + auto& change_set = changes_list_[idx][address]; + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD && !change_set.empty()){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); + } + return; +} + + +// ========================================== + +void StreamingEController::AppendPreRecord(const uint256_t& address, int hash_idx, + const std::vector& commit_id) { + //std::lock_guard lk(mutex_[hash_idx]); + std::unique_lock lock(mutex_[hash_idx]); + auto& commit_set = pre_commit_list_[hash_idx][address]; + for(auto id : commit_id){ + //LOG(ERROR)<<"roll back id:"< lk(mutex_[idx]); + std::unique_lock lock(mutex_[idx]); + auto it = pre_commit_list_[idx].find(address); + if(it == pre_commit_list_[idx].end()){ + //LOG(ERROR)<<" remove address:"<first != commit_id){ + //LOG(ERROR)<< "address id::"<first; + return -2; + } + //LOG(ERROR)<< "address id::"<first; +} + + +void StreamingEController::Remove(int64_t commit_id){ + //LOG(ERROR)<<"remove:"< new_commit_ids; + for(auto it : rechanges_list_[idx]){ + const uint256_t& addr = it.first; + int64_t next_commit_id = RemovePrecommitRecord(addr, commit_id); + + //LOG(ERROR)<<"remove id:"< 0 && next_commit_id <= last_pre_commit_id_){ + //LOG(ERROR)<<"commit id:"<second; + if(commit_set.empty()){ + //LOG(ERROR)<<"commit set is empty:"< commit_id){ + //LOG(ERROR)<<"set header:"< lk(mutex_[idx]); + std::shared_lock lock(mutex_[idx]); + const auto& it = pre_commit_list_[idx].find(address); + if(it == pre_commit_list_[idx].end()){ + //LOG(ERROR)<<"no address:"<second; + if(commit_set.empty()){ + //LOG(ERROR)<<"commit set is empty:"<first < commit_id){ + // not the first candidate + //LOG(ERROR)<<"still have dep. commit id:"<first<<" address:"<first > commit_id){ + //LOG(ERROR)<<"set header:"<first<<" current:"<first == commit_id); + return true; +} + + +bool StreamingEController::CheckCommit(int64_t commit_id, bool is_pre){ + int idx = commit_id&window_size_; + //LOG(ERROR)<<"check commit "<GetVersion(it.first, is_pre); + //LOG(ERROR)<<"op log:"<Reset(address, data.data, data.version, true); + }else { + storage_->Reset(address, data.old_data, data.version, true); + } +} + + +void StreamingEController::RedoConflict(int64_t commit_id) { + //LOG(ERROR)<<"move conflict id:"<, std::greater>q; + std::set v; + q.push(commit_id); + v.insert(commit_id); + + while(!q.empty()){ + int64_t cur_id = q.top(); + q.pop(); + const auto& change_set = changes_list_[cur_id&window_size_]; + + for(const auto& it : change_set){ + const uint256_t& address = it.first; + int hash_idx = GetHashKey(address); + if(commit_list_[hash_idx].find(address) == commit_list_[hash_idx].end()){ + continue; + } + //LOG(ERROR)<<"check commit id:"<version; + auto& commit_set = commit_list_[hash_idx][address]; + + std::vector back_list; + while(!commit_set.empty() && commit_set.back() >= cur_id){ + int64_t back_id = commit_set.back(); + commit_set.pop_back(); + + back_list.push_back(back_id); + if(v.find(back_id) == v.end()){ + q.push(back_id); + v.insert(back_id); + } + if(back_id == cur_id) { + break; + } + } + //LOG(ERROR)<<"hahs list size:"<second.begin()); + } + if(cur_id == commit_id){ + back_list.push_back(commit_id); + } + AppendPreRecord(address, hash_idx, back_list); + } + } +} + + +bool StreamingEController::CheckPreCommit(int64_t commit_id) { + //LOG(ERROR)<<"check pre commit:"< commit_id){ + return false; + } + } + return true; +} + + +bool StreamingEController::PreCommit(int64_t commit_id){ + //LOG(ERROR)<<" precommit :"< new_commit_ids; + for(const auto& it : change_set){ + const uint256_t& address = it.first; + //LOG(ERROR)<<"get pre-op:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, true); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, true); + done = true; + break; + } + } + + //LOG(ERROR)<<"append id:"<=-1); + if(next_commit_id> 0 && next_commit_id <= last_pre_commit_id_){ + //LOG(ERROR)<<"commit id:"<last_pre_commit_id_){ + assert(1==0); + } + */ + } + + for(int64_t redo_commit: new_commit_ids){ + RedoCommit(redo_commit, 1); + } + //LOG(ERROR)<<"done:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + RemoveRecord(address, commit_id, commit_list_); + } + + CommitDone(commit_id); + Remove(commit_id); + assert(last_commit_id_ == commit_id); + last_commit_id_++; + return true; +} + + +bool StreamingEController::Commit(int64_t commit_id){ + redo_.clear(); + done_.clear(); + is_redo_[commit_id&window_size_] = false; + //LOG(ERROR)<<"====================== commit id:"< +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/d_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class StreamingEController : public ConcurrencyController { + public: + StreamingEController(DataStorage * storage, int window_size); + virtual ~StreamingEController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + typedef std::map > PreCommitList; + + std::vector& GetRedo(); + std::vector& GetDone(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + void SetPrecommitCallback( std::function precommit_callback); + + private: + void Clear(); + + + bool PreCommit(int64_t commit_id); + bool PostCommit(int64_t commit_id); + + void RedoConflict(int64_t commit_id); + bool CheckPreCommit(int64_t commit_id); + bool CheckFirstFromPreCommit(const uint256_t& address, int64_t commit_id); + bool CheckFirstFromCommit(const uint256_t& address, int64_t commit_id); + /* + bool PreCommitInternal(int64_t commit_id); + void MergeChangeList(int64_t commit_id); + + + bool CheckCommit(int64_t commit_id, const CommitList *commit_list, bool is_pre); + + const ModifyMap * GetChangeList(int64_t commit_id); + */ + void Remove(int64_t commit_id); + void CleanOldData(int64_t commit_id); + + int64_t RemovePrecommitRecord(const uint256_t& address, int64_t commit_id); + + bool CheckCommit(int64_t commit_id, bool is_pre); + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + +/* + + bool IsRead(const uint256_t& address, int64_t commit_id); + + */ + + void RollBackData(const uint256_t& address, const Data& data); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data, int version); + void AppendPreRecord(const uint256_t& address, int hash_idx, + const std::vector& commit_id); + + private: + int window_size_ = 1000; + + std::vector changes_list_,rechanges_list_; + + CommitList commit_list_[1024]; + int64_t last_commit_id_, current_commit_id_; + + PreCommitList pre_commit_list_[1024] GUARDED_BY(mutex_); + int64_t last_pre_commit_id_; + + std::atomic is_redo_[1024]; + std::vector redo_; + + //std::atomic is_done_[1024]; + std::vector done_; + + D_Storage * storage_; + //std::mutex mutex_[1024]; + mutable std::shared_mutex mutex_[1024]; + + + std::function precommit_callback_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_committer.cpp b/platform/consensus/ordering/fides/executor/paral_sm/test_committer.cpp new file mode 100644 index 000000000..e69912d0b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_committer.cpp @@ -0,0 +1,80 @@ +/* + * 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 "service/contract/executor/manager/test_committer.h" + +#include "glog/logging.h" +//#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +TestCommitter:: TestCommitter( + DataStorage* storage, + GlobalState * global_state):gs_(global_state) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); +} + +TestCommitter::~TestCommitter(){ +} + +std::vector> TestCommitter::ExecContract( + const std::vector& requests) { + + std::vector > resp_list; + for(const auto& request: requests){ + std::unique_ptr resp = std::make_unique(); + //auto start_time = GetCurrentTime(); + auto ret = ExecContract(request.caller_address, request.contract_address, + request.func_addr, + request.func_params, gs_); + resp->state = ret.status(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + } + else { + LOG(ERROR)<<"exec fail"; + resp->ret = -1; + } + resp->contract_address = request.contract_address; + resp->commit_id = request.commit_id; + + resp_list.push_back(std::move(resp)); + } + return resp_list; +} + +absl::StatusOr TestCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_committer.h b/platform/consensus/ordering/fides/executor/paral_sm/test_committer.h new file mode 100644 index 000000000..0d98bafba --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_committer.h @@ -0,0 +1,63 @@ +/* + * 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 "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/test_controller.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" + +namespace resdb { +namespace contract { + +class TestCommitter : public ContractCommitter { + public: + TestCommitter( + DataStorage* storage, + GlobalState * global_state); + + virtual ~TestCommitter(); + + std::vector> ExecContract(const std::vector& request); + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + GlobalState* gs_; + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_controller.cpp b/platform/consensus/ordering/fides/executor/paral_sm/test_controller.cpp new file mode 100644 index 000000000..97edd57ed --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_controller.cpp @@ -0,0 +1,35 @@ +#include "service/contract/executor/manager/test_controller.h" + +#include + +namespace resdb { +namespace contract { + +TestController::TestController(DataStorage * storage) : ConcurrencyController(storage){} + +void TestController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + for(auto it : local_changes){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return ; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_controller.h b/platform/consensus/ordering/fides/executor/paral_sm/test_controller.h new file mode 100644 index 000000000..a4371033a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_controller.h @@ -0,0 +1,19 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +#include +#include + +namespace resdb { +namespace contract { + +class TestController : public ConcurrencyController { + public: + TestController(DataStorage * storage); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_data/BUILD b/platform/consensus/ordering/fides/executor/paral_sm/test_data/BUILD new file mode 100644 index 000000000..6f9052157 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_data/BUILD @@ -0,0 +1 @@ +exports_files(["contract.json", "kv.json"]) diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_data/compile.sh b/platform/consensus/ordering/fides/executor/paral_sm/test_data/compile.sh new file mode 100644 index 000000000..dfd25e66f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_data/compile.sh @@ -0,0 +1,5 @@ +# sudo add-apt-repository ppa:ethereum/ethereum +# sudo apt-get update +# sudo apt-get install solc +solc --evm-version homestead --combined-json bin,hashes --pretty-json --optimize kv.sol > kv.json + diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_data/contract.json b/platform/consensus/ordering/fides/executor/paral_sm/test_data/contract.json new file mode 100644 index 000000000..e9c0d972f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_data/contract.json @@ -0,0 +1,19 @@ +{ + "contracts": + { + "ERC20.sol:ERC20Token": + { + "bin": "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a72315820e4ae92c4517e475d9f77ac0bfcd10463a09239f34734fc0ab4d7966450fb428464736f6c63430005100032", + "hashes": + { + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.json b/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.json new file mode 100644 index 000000000..87891db72 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.json @@ -0,0 +1,18 @@ +{ + "contracts": + { + "kv.sol:KV": + { + "bin": "608060405234801561001057600080fd5b5060405161052338038061052383398101604081905261002f91610044565b3360009081526020819052604090205561005d565b60006020828403121561005657600080fd5b5051919050565b6104b78061006c6000396000f3fe608060405234801561001057600080fd5b5060043610610073577c01000000000000000000000000000000000000000000000000000000006000350463239b83eb81146100785780633825d828146100a0578063beabacc8146100b3578063c2bc2efc146100c6578063cfc9c3f9146100fd575b600080fd5b61008b610086366004610359565b610110565b60405190151581526020015b60405180910390f35b61008b6100ae3660046103a4565b6101d6565b61008b6100c13660046103ce565b61021e565b6100ef6100d436600461040a565b600160a060020a031660009081526020819052604090205490565b604051908152602001610097565b61008b61010b3660046103ce565b6102a3565b600160a060020a0384166000908152602081905260408120548281111561015f57600160a060020a0386166000908152602081905260408120805485929061015990849061045b565b90915550505b61032081101561019c57600160a060020a0385166000908152602081905260408120805485929061019190849061046e565b909155506101ca9050565b600160a060020a038416600090815260208190526040812080548592906101c490849061046e565b90915550505b50600195945050505050565b600160a060020a0382166000908152602081905260408120546101f9818461046e565b600160a060020a03851660009081526020819052604090205550600190505b92915050565b600160a060020a03831660009081526020819052604081205482101561026c57600160a060020a0384166000908152602081905260408120805484929061026690849061045b565b90915550505b600160a060020a0383166000908152602081905260408120805484929061029490849061046e565b90915550600195945050505050565b3360009081526020819052604081208054908390836102c2838561045b565b909155505061032081101561030457600160a060020a038516600090815260208190526040812080548592906102f990849061046e565b909155506103329050565b600160a060020a0384166000908152602081905260408120805485929061032c90849061046e565b90915550505b506001949350505050565b8035600160a060020a038116811461035457600080fd5b919050565b6000806000806080858703121561036f57600080fd5b6103788561033d565b93506103866020860161033d565b92506103946040860161033d565b9396929550929360600135925050565b600080604083850312156103b757600080fd5b6103c08361033d565b946020939093013593505050565b6000806000606084860312156103e357600080fd5b6103ec8461033d565b92506103fa6020850161033d565b9150604084013590509250925092565b60006020828403121561041c57600080fd5b6104258261033d565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156102185761021861042c565b808201808211156102185761021861042c56fea2646970667358221220cfe0598bf227525b3a229778738f57a355eb5f0f171527891e3413c58bbffbd964736f6c63430008130033", + "hashes": + { + "get(address)": "c2bc2efc", + "set(address,uint256)": "3825d828", + "transfer(address,address,uint256)": "beabacc8", + "transferif(address,address,address,uint256)": "239b83eb", + "transferto(address,address,uint256)": "cfc9c3f9" + } + } + }, + "version": "0.8.19+commit.7dd6d404.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.sol b/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.sol new file mode 100644 index 000000000..f5bfed426 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/test_data/kv.sol @@ -0,0 +1,59 @@ +pragma solidity >= 0.5.0; + +// Transfer tokens from the contract owner +contract KV { + mapping (address => uint256) balances; + mapping (address => uint256) allow; + + constructor(uint256 s) public { + balances[msg.sender] = s; + } + + // Get the account balance of another account with address _owner + function get(address _owner) public view returns (uint256) { + return balances[_owner]; + } + + // Send _value amount of tokens to address _to + function set(address _to, uint256 _value) public returns (bool) { + uint256 values = balances[_to]; + balances[_to] = _value + values; + return true; + } + + // Send _value amount of tokens to address _to + function transfer(address _from, address _to, uint256 _value) public returns (bool) { + if (balances[_from] > _value) { + balances[_from] -= _value; + } + balances[_to] += _value; + return true; + } + + function transferif(address _from, address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[_from]; + + if (balances[_from] > _value) { + balances[_from] -= _value; + } + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + + function transferto(address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[msg.sender]; + balances[msg.sender] -= _value; + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + +} + diff --git a/platform/consensus/ordering/fides/executor/paral_sm/utils.h b/platform/consensus/ordering/fides/executor/paral_sm/utils.h new file mode 100644 index 000000000..2d243d66f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/utils.h @@ -0,0 +1,36 @@ +/* + * 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 "eEVM/address.h" + +namespace resdb { +namespace contract { + +typedef eevm::Address Address; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/xcommitter.h b/platform/consensus/ordering/fides/executor/paral_sm/xcommitter.h new file mode 100644 index 000000000..eaed943b4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/xcommitter.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class StreamingECommitter : public ContractCommitter { + public: + StreamingECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingECommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/xcontroller.cpp b/platform/consensus/ordering/fides/executor/paral_sm/xcontroller.cpp new file mode 100644 index 000000000..41e8a5bd6 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/xcontroller.cpp @@ -0,0 +1,605 @@ +#include "service/contract/executor/paral_sm/xcontroller.h" + +#include +#include +#include + +namespace resdb { +namespace contract { +namespace paral_sm { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + return v%128; + } + + bool CheckFirstCommit(const uint256_t& address, int64_t commit_id, + const XController::CommitList *commit_list, bool is_pre){ + int idx = GetHashKey(address); + const auto& it = commit_list[idx].find(address); + if(it == commit_list[idx].end()){ + if(is_pre){ + LOG(ERROR)<<"no address:"<second; + if(commit_set.empty()){ + LOG(ERROR)<<"commit set is empty:"< commit_id){ + LOG(ERROR)<<"set header:"<<*commit_set.begin()<<" current:"< commit_id){ + LOG(ERROR)<<"append to old data:"<& commit_id, XController::CommitList * commit_list) { + auto& commit_set = commit_list[hash_idx][address]; + for(auto id : commit_id){ + //LOG(ERROR)<<"push front id:"<second; + std::stack tmp; + while(!set_it.empty()){ + if(set_it.front() == commit_id){ + break; + } + tmp.push(set_it.front()); + set_it.pop_front(); + } + set_it.pop_front(); + while(!tmp.empty()){ + set_it.push_front(tmp.top()); + tmp.pop(); + } + + if(set_it.empty()) { + //LOG(ERROR)<<"erase idx:"<(storage)){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1<& XController::GetRedo(){ + return redo_; +} + +std::vector& XController::GetDone(){ + return done_; +} + +void XController::RedoCommit(int64_t commit_id, int flag) { + int idx = commit_id&window_size_; + + if(is_redo_[idx]==1){ + LOG(ERROR)<<"commit id has been redo:"< commit_id){ + return false; + } + } + return true; +} + + +bool XController::CheckCommit(int64_t commit_id, const CommitList * commit_list, + bool is_pre) { + const auto& change_set = changes_list_[commit_id&window_size_]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<GetVersion(it.first, is_pre); + //LOG(ERROR)<<"op log:"<Reset(address, data.data, data.version, /*is_pre=*/true); + }else { + storage_->Reset(address, data.old_data, data.version, /*is_pre=*/true); + } +} + +// move id from commitlist back to precommitlist +void XController::RedoConflict(int64_t commit_id) { + //LOG(ERROR)<<"move conflict id:"<, std::greater>q; + std::set v; + q.push(commit_id); + v.insert(commit_id); + + while(!q.empty()){ + int64_t cur_id = q.top(); + q.pop(); + const auto& change_set = changes_list_[cur_id&window_size_]; + + for(const auto& it : change_set){ + const uint256_t& address = it.first; + int hash_idx = GetHashKey(address); + if(commit_list_[hash_idx].find(address) == commit_list_[hash_idx].end()){ + continue; + } + //LOG(ERROR)<<"check commit id:"<version; + auto& commit_set = commit_list_[hash_idx][address]; + + if(cur_id == commit_id){ + auto& precommit_set = pre_commit_list_[hash_idx][address]; + assert(*precommit_set.begin() == commit_id); + precommit_set.pop_front(); + } + + std::vector back_list; + while(!commit_set.empty() && commit_set.back() >= cur_id){ + int64_t back_id = commit_set.back(); + commit_set.pop_back(); + + back_list.push_back(back_id); + if(v.find(back_id) == v.end()){ + q.push(back_id); + v.insert(back_id); + } + if(back_id == cur_id) { + break; + } + } + //LOG(ERROR)<<"hahs list size:"<second.begin()); + } + if(cur_id == commit_id){ + back_list.push_back(commit_id); + } + AppendHeadRecord(address, hash_idx, back_list, pre_commit_list_); + /* + for(int64_t id : pre_commit_list_[hash_idx][address]){ + LOG(ERROR)<<" addr:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + RemoveRecord(address, commit_id, commit_list_); + } + + CommitDone(commit_id); + Remove(commit_id); + assert(last_commit_id_ == commit_id); + last_commit_id_++; + return true; +} + +bool XController::PreCommitInternal(int64_t commit_id){ + if(!CheckCommit(commit_id, pre_commit_list_, true)){ + //LOG(ERROR)<<"check commit fail:"< new_commit_ids; + for(const auto& it : change_set){ + const uint256_t& address = it.first; + //LOG(ERROR)<<"get pre-op:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data, true); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, true); + done = true; + break; + } + } + + //LOG(ERROR)<<"append id:"<=-1); + if(next_commit_id> 0 && next_commit_id <= last_pre_commit_id_){ + //LOG(ERROR)<<"commit id:"<last_pre_commit_id_){ + assert(1==0); + } + } + + + for(int64_t redo_commit: new_commit_ids){ + RedoCommit(redo_commit, 1); + } + return true; +} + +void XController::MergeChangeList(int64_t commit_id){ + int idx = commit_id&window_size_; + if(rechanges_list_[idx].empty()){ + return; + } + + std::set new_list; + for(const auto& new_addr: rechanges_list_[idx]) { + new_list.insert(new_addr.first); + } + + for(const auto& old_addr : changes_list_[idx]){ + if(new_list.find(old_addr.first) == new_list.end()){ + //int64_t next_commit_id = RemoveRecord(old_addr.first, commit_id, pre_commit_list_); + int64_t next_commit_id = RemoveFromRecord(old_addr.first, commit_id, pre_commit_list_); + + //LOG(ERROR)<<" old addr:"<=-1); + if(next_commit_id> 0 && next_commit_id <= last_pre_commit_id_ && next_commit_id > commit_id){ + //LOG(ERROR)<<"commit id:"< commit_id); + commit_set.push_front(commit_id); + } + else { + std::stack tmp; + while(!commit_set.empty() && commit_set.front() <= commit_id){ + tmp.push(commit_set.front()); + commit_set.pop_front(); + } + if(tmp.empty() || tmp.top() != commit_id){ + tmp.push(commit_id); + } + while(!tmp.empty()){ + commit_set.push_front(tmp.top()); + tmp.pop(); + } + //LOG(ERROR)<<"queue size:"<last_pre_commit_id_){ + assert(rechanges_list_[commit_id&window_size_].empty()); + + const auto & local_changes = changes_list_[commit_id&window_size_]; + //LOG(ERROR)<<"commit :"< +#include +#include +#include + +#include "service/contract/executor/paral_sm/concurrency_controller.h" +#include "service/contract/executor/paral_sm/d_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +class XController : public ConcurrencyController { + public: + XController(DataStorage * storage, int window_size); + ~XController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + + std::vector& GetRedo(); + std::vector& GetDone(); + + typedef std::map > CommitList; + + private: + void Clear(); + + + bool PreCommit(int64_t commit_id); + bool PreCommitInternal(int64_t commit_id); + void MergeChangeList(int64_t commit_id); + + bool PostCommit(int64_t commit_id); + void RedoConflict(int64_t commit_id); + + bool CheckCommit(int64_t commit_id, const CommitList *commit_list, bool is_pre); + bool CheckPreCommit(int64_t commit_id); + + const ModifyMap * GetChangeList(int64_t commit_id); + void Remove(int64_t commit_id); + + + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + + void RollBackData(const uint256_t& address, const Data& data); + + bool IsRead(const uint256_t& address, int64_t commit_id); + + private: + int window_size_ = 1000; + + std::vector changes_list_,rechanges_list_; + + CommitList commit_list_[1024]; + int64_t last_commit_id_, current_commit_id_; + + CommitList pre_commit_list_[1024]; + int64_t last_pre_commit_id_; + + std::atomic is_redo_[1024]; + std::vector redo_; + + //std::atomic is_done_[1024]; + std::vector done_; + + D_Storage * storage_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.cpp b/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.cpp new file mode 100644 index 000000000..1032128f4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.cpp @@ -0,0 +1,289 @@ +/* + * 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 "service/contract/executor/paral_sm/xexecutor.h" + +#include +#include + +#include "service/contract/executor/paral_sm/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + + +namespace resdb { +namespace contract { +namespace paral_sm { + +XExecutor:: XExecutor( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + //LOG(ERROR)<<"init window:"<(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request= request_queue_.Pop(); + if (request== nullptr) { + continue; + } + + //std::unique_ptr request = std::move(*request_ptr); + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id<<" param:"<< + // request->GetContractExecuteInfo()->func_params.DebugString(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + assert(resp->ret>=0); + } + request->SetResult(std::move(resp)); + resp_queue_.Push(std::move(request)); + } + })); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void XExecutor::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +XExecutor::~XExecutor(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +/* +void XExecutor::CallBack(uint64_t commit_id){ + //LOG(ERROR)<<"call back:"< lk(mutex_); + cv_.notify_all(); + } + //LOG(ERROR)<<"call back done:"< lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return id_ - first_id_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + //LOG(ERROR)<<"recv :"< q; + q.push(resp_commit_id); + while(!q.empty()){ + int64_t next_id = q.front(); + q.pop(); + + bool ret = controller_->Commit(next_id); + std::vector next_commit = controller_->GetRedo(); + //LOG(ERROR)<<"redo size:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + else { + q.push(new_next); + } + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<commit_id; + //LOG(ERROR)<<" !!!!! commit new resp:"<Commit(current_commit_id); + if(!ret){ + //LOG(ERROR)<<"redo size:"<GetRedo().size(); + if(controller_->GetRedo().size()){ + assert(controller_->GetRedo()[0] == current_commit_id); + //redo_list.push(commit_id); + //LOG(ERROR)<<"commit redo:"<SetRedo(); + //LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + else { + std::vector list = controller_->GetRedo(); + assert(list.empty()); + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<& requests) { + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + request_queue_.Push(std::make_unique(request)); + } + + return ; +} + +absl::StatusOr XExecutor::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.h b/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.h new file mode 100644 index 000000000..4334c113d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/paral_sm/xexecutor.h @@ -0,0 +1,104 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/paral_sm/global_state.h" +#include "service/contract/executor/paral_sm/xcontroller.h" +#include "service/contract/executor/paral_sm/contract_committer.h" +#include "service/contract/executor/paral_sm/contract_executor.h" +#include "service/contract/executor/paral_sm/committer_context.h" +#include "service/contract/executor/paral_sm/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace paral_sm { + +class XExecutor : public ContractCommitter { + public: + XExecutor( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~XExecutor(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/service/BUILD b/platform/consensus/ordering/fides/executor/service/BUILD new file mode 100644 index 000000000..46776a78a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/BUILD @@ -0,0 +1,26 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "contract_transaction_manager", + srcs = ["contract_transaction_manager.cpp"], + hdrs = ["contract_transaction_manager.h"], + deps = [ + "//platform/config:resdb_config_utils", + "//service/contract/executor/manager:address_manager", + "//service/contract/executor/manager:contract_manager", + "//service/contract/proto:rpc_cc_proto", + "//service/utils:server_factory", + ], +) + +cc_test( + name = "contract_transaction_manager_test", + srcs = ["contract_transaction_manager_test.cpp"], + data = [ + "//service/contract/executor/service/test_data:contract.json", + ], + deps = [ + ":contract_transaction_manager", + "//common/test:test_main", + ], +) diff --git a/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.cpp b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.cpp new file mode 100644 index 000000000..acc365cc7 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.cpp @@ -0,0 +1,127 @@ +/* + * 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 "service/contract/executor/service/contract_transaction_manager.h" + +#include + +namespace resdb { +namespace contract { + +ContractTransactionManager::ContractTransactionManager(void) + : contract_manager_(std::make_unique(std::make_unique())), + address_manager_(std::make_unique()) {} + +std::unique_ptr ContractTransactionManager::ExecuteData( + const std::string& client_request) { + Request request; + Response response; + + if (!request.ParseFromString(client_request)) { + LOG(ERROR) << "parse data fail in ContractTransactionManager"; + return nullptr; + } + + int ret = 0; + if (request.cmd() == Request::CREATE_ACCOUNT) { + absl::StatusOr account_or = CreateAccount(); + if (account_or.ok()) { + response.mutable_account()->Swap(&(*account_or)); + } else { + ret = -1; + } + } else if (request.cmd() == Request::DEPLOY) { + absl::StatusOr contract_or = Deploy(request); + if (contract_or.ok()) { + response.mutable_contract()->Swap(&(*contract_or)); + } else { + ret = -1; + } + } else if (request.cmd() == Request::EXECUTE) { + auto res_or = Execute(request); + if (res_or.ok()) { + response.set_res(*res_or); + } else { + ret = -1; + } + } + + response.set_ret(ret); + + std::unique_ptr resp_str = std::make_unique(); + if (!response.SerializeToString(resp_str.get())) { + return nullptr; + } + + return resp_str; +} + +absl::StatusOr ContractTransactionManager::CreateAccount() { + std::string address = + AddressManager::AddressToHex(address_manager_->CreateRandomAddress()); + Account account; + account.set_address(address); + return account; +} + +absl::StatusOr ContractTransactionManager::Deploy( + const Request& request) { + Address caller_address = + AddressManager::HexToAddress(request.caller_address()); + if (!address_manager_->Exist(caller_address)) { + LOG(ERROR) << "caller doesn't have an account"; + return absl::InvalidArgumentError("Account not exist."); + } + + Address contract_address = + contract_manager_->DeployContract(caller_address, request.deploy_info()); + + if (contract_address > 0) { + Contract contract; + contract.set_owner_address(request.caller_address()); + contract.set_contract_address( + AddressManager::AddressToHex(contract_address)); + contract.set_contract_name(request.deploy_info().contract_name()); + return contract; + } + return absl::InternalError("Deploy Contract fail."); +} + +absl::StatusOr ContractTransactionManager::Execute( + const Request& request) { + Address caller_address = + AddressManager::HexToAddress(request.caller_address()); + if (!address_manager_->Exist(caller_address)) { + LOG(ERROR) << "caller doesn't have an account"; + return absl::InvalidArgumentError("Account not exist."); + } + + return contract_manager_->ExecContract( + caller_address, AddressManager::HexToAddress(request.contract_address()), + request.func_params()); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.h b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.h new file mode 100644 index 000000000..33638c2f4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager.h @@ -0,0 +1,56 @@ +/* + * 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 "platform/config/resdb_config_utils.h" +#include "platform/consensus/execution/transaction_manager.h" +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/contract_manager.h" +#include "service/contract/proto/func_params.pb.h" +#include "service/contract/proto/rpc.pb.h" + +namespace resdb { +namespace contract { + +class ContractTransactionManager : public TransactionManager { + public: + ContractTransactionManager(void); + virtual ~ContractTransactionManager() = default; + + std::unique_ptr ExecuteData(const std::string& request) override; + + private: + absl::StatusOr CreateAccount(); + absl::StatusOr Deploy(const Request& request); + absl::StatusOr Execute(const Request& request); + + private: + std::unique_ptr contract_manager_; + std::unique_ptr address_manager_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/service/contract_transaction_manager_test.cpp b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager_test.cpp new file mode 100644 index 000000000..e0d59e186 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/contract_transaction_manager_test.cpp @@ -0,0 +1,270 @@ +/* + * 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 "service/contract/executor/service/contract_transaction_manager.h" + +#include +#include + +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/service/"; + +std::string ToString(const Request& request) { + std::string ret; + request.SerializeToString(&ret); + return ret; +} + +class ContractTransactionManagerTest : public Test { + public: + ContractTransactionManagerTest() { + std::string contract_path = test_dir + "test_data/contract.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + nlohmann::json definition = nlohmann::json::parse(contract_fstream); + contracts_json_ = definition["contracts"]; + } + + Account CreateAccount() { + Request request; + Response response; + + request.set_cmd(Request::CREATE_ACCOUNT); + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + response.ParseFromString(*ret); + return response.account(); + } + + absl::StatusOr Deploy(const Account& account, + DeployInfo deploy_info) { + Request request; + Response response; + + request.set_caller_address(account.address()); + *request.mutable_deploy_info() = deploy_info; + request.set_cmd(Request::DEPLOY); + + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + + response.ParseFromString(*ret); + if (response.ret() == 0) { + return response.contract(); + } else { + return absl::InternalError("DeployFail."); + } + } + + absl::StatusOr Execute(const std::string& caller_address, + const std::string& contract_address, + const Params& params) { + Request request; + Response response; + + request.set_caller_address(caller_address); + request.set_contract_address(contract_address); + request.set_cmd(Request::EXECUTE); + *request.mutable_func_params() = params; + + std::unique_ptr ret = executor_.ExecuteData(ToString(request)); + EXPECT_TRUE(ret != nullptr); + + response.ParseFromString(*ret); + + if (response.ret() == 0) { + return eevm::to_uint256(response.res()); + } else { + return absl::InternalError("DeployFail."); + } + } + + protected: + nlohmann::json contracts_json_; + ContractTransactionManager executor_; +}; + +TEST_F(ContractTransactionManagerTest, ExecContract) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_TRUE(contract_or.ok()); + Contract contract = *contract_or; + + // query owner should return 1000 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 0x3e8); + } + + Account transfer_receiver = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(transfer_receiver.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(transfer_receiver.address()); + func_params.add_param("400"); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 1); + } + + // query owner should return 600 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 600); + } + + // receiver 400 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(transfer_receiver.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_EQ(*result, 400); + } +} + +TEST_F(ContractTransactionManagerTest, DeployFail) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + // deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_FALSE(contract_or.ok()); +} + +TEST_F(ContractTransactionManagerTest, NoFunc) { + // create an account. + Account account = CreateAccount(); + EXPECT_FALSE(account.address().empty()); + + std::string contract_name = "ERC20.sol:ERC20Token"; + std::string contract_code = contracts_json_[contract_name]["bin"]; + nlohmann::json func_hashes = contracts_json_[contract_name]["hashes"]; + + // deploy + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_code); + deploy_info.set_contract_name(contract_name); + + for (auto& func : func_hashes.items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + deploy_info.add_init_param("1000"); + + absl::StatusOr contract_or = Deploy(account, deploy_info); + EXPECT_TRUE(contract_or.ok()); + Contract contract = *contract_or; + + { + Params func_params; + func_params.set_func_name("balanceOf()"); + func_params.add_param(account.address()); + + auto result = + Execute(account.address(), contract.contract_address(), func_params); + EXPECT_FALSE(result.ok()); + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/service/test_data/BUILD b/platform/consensus/ordering/fides/executor/service/test_data/BUILD new file mode 100644 index 000000000..65e3dc3d9 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/test_data/BUILD @@ -0,0 +1 @@ +exports_files(["contract.json"]) diff --git a/platform/consensus/ordering/fides/executor/service/test_data/contract.json b/platform/consensus/ordering/fides/executor/service/test_data/contract.json new file mode 100644 index 000000000..e9c0d972f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/service/test_data/contract.json @@ -0,0 +1,19 @@ +{ + "contracts": + { + "ERC20.sol:ERC20Token": + { + "bin": "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a72315820e4ae92c4517e475d9f77ac0bfcd10463a09239f34734fc0ab4d7966450fb428464736f6c63430005100032", + "hashes": + { + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.cpp new file mode 100644 index 000000000..c73212873 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.cpp @@ -0,0 +1,249 @@ +/* + * 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 "service/contract/executor/x_manager/2pl_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +TwoPLCommitter:: TwoPLCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + //LOG(ERROR)<<"init window:"<(storage, window_size); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + resp->runtime = track.GetRunTime()*1000; + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + + +TwoPLCommitter::~TwoPLCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void TwoPLCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +ExecutionContext* TwoPLCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void TwoPLCommitter::AsyncExecContract(std::vector& requests) { + + return ; +} + +std::vector> TwoPLCommitter::ExecContract( + std::vector& requests) { + + controller_->Clear(); + int id = 1; + std::vector> tmp_resp_list; + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + context_ptr->start_time = GetCurrentTime(); + + AddTask(request.commit_id, std::move(context)); + request_queue_.Push(std::make_unique(context_ptr)); + tmp_resp_list.push_back(nullptr); + } + + tmp_resp_list.push_back(nullptr); + std::vector> resp_list; + + int process_num = id-1; + while(process_num>0) { + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + + int64_t resp_id= resp->commit_id; + int ret = resp->ret; + #ifdef Debug + LOG(ERROR)<<"resp:"<Commit(resp_id); + if(!commit_ret){ + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + const auto& redo_list = controller_->GetRedo(); + for(int64_t id : redo_list) { + controller_->Clear(id); + auto context_ptr = GetTaskContext(id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + + const auto& done_list = controller_->GetDone(); + for(int id : done_list){ + //tmp_resp_list[id]->rws = *controller_->GetChangeList(id); + //LOG(ERROR)<<"done :"<rws.size(); + resp_list.push_back(std::move(tmp_resp_list[id])); + process_num--; + continue; + } + } + else { + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + return resp_list; +// LOG(ERROR)<<"last id:"< TwoPLCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.h b/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.h new file mode 100644 index 000000000..f1f5ea732 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/2pl_committer.h @@ -0,0 +1,106 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/2pl_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class TwoPLCommitter : public ContractCommitter { + public: + TwoPLCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~TwoPLCommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + //std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.cpp new file mode 100644 index 000000000..2c7edbb9d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.cpp @@ -0,0 +1,297 @@ +#include "service/contract/executor/x_manager/2pl_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +TwoPLController::TwoPLController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1<second != commit_id){ + // LOG(ERROR)<<"append address:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; +#ifdef CDebug + LOG(ERROR)<<"LOAD from db:"<<" address:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit addr:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + } + ReleaseLock(commit_id); + done_.push_back(commit_id); + #ifdef CDebug + LOG(ERROR)<<"commit done:"<< commit_id; + #endif + return true; +} + + +bool TwoPLController::Commit(int64_t commit_id){ + #ifdef CDebug + LOG(ERROR)<<"commit id:"< &TwoPLController::GetRedo(){ + return redo_; +} +const std::vector &TwoPLController::GetDone(){ + return done_; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.h b/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.h new file mode 100644 index 000000000..a105e2e39 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/2pl_controller.h @@ -0,0 +1,108 @@ +#pragma once + +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class TwoPLController : public ConcurrencyController { + public: + TwoPLController(DataStorage * storage, int window_size); + virtual ~TwoPLController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + ConcurrencyController::ModifyMap * GetChangeList(int64_t commit_id); + + void Clear(); +void Clear(int64_t commit_id); + + private: + //void Clear(); + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + void RemovePreRecord(const uint256_t& address, + int64_t commit_id); + + enum LockType { + READ = 1, + WRITE = 2 + }; + + bool TryLock(const uint256_t& address, int64_t owner, LockType type); + void ReleaseLock(int64_t commit_id); + void ReleaseLock(int64_t commit_id, const uint256_t& address); + + void Abort(const int64_t commit_id); + +void AddRedo(int64_t commit_id); + std::vector FetchAbort(); + + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo(){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + std::vector changes_list_; + //std::vector> changes_list_; + typedef std::map> > PreCommitList; + PreCommitList pre_commit_list_ GUARDED_BY(mutex_); + std::map> last_; + //PreCommitList pre_commit_list_[4096] GUARDED_BY(mutex_); + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, db_mutex_; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + std::vector abort_list_; + std::map lock_table_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/BUILD b/platform/consensus/ordering/fides/executor/x_manager/BUILD new file mode 100644 index 000000000..414b9b8ce --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/BUILD @@ -0,0 +1,576 @@ +package(default_visibility = ["//visibility:public"]) + +cc_library( + name = "utils", + hdrs = ["utils.h"], + deps = [ + "//third_party:evm_lib", + ], +) + +cc_library( + name = "address_manager", + srcs = ["address_manager.cpp"], + hdrs = ["address_manager.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_test( + name = "address_manager_test", + srcs = ["address_manager_test.cpp"], + deps = [ + ":address_manager", + "//common/test:test_main", + ], +) + +cc_library( + name = "data_storage", + srcs = ["data_storage.cpp"], + hdrs = ["data_storage.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_library( + name = "leveldb_storage", + srcs = ["leveldb_storage.cpp"], + hdrs = ["leveldb_storage.h"], + deps = [ + ":data_storage", + "//storage:res_leveldb", + "//common:comm", + ], +) + + + +cc_library( + name = "mock_data_storage", + hdrs = ["mock_data_storage.h"], + deps = [ + ":data_storage", + "//common/test:test" + ], +) + +cc_library( + name = "mock_d_storage", + hdrs = ["mock_d_storage.h"], + deps = [ + ":d_storage", + "//common/test:test" + ], +) + + +cc_library( + name = "d_storage", + srcs = ["d_storage.cpp"], + hdrs = ["d_storage.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + +cc_library( + name = "leveldb_d_storage", + srcs = ["leveldb_d_storage.cpp"], + hdrs = ["leveldb_d_storage.h"], + deps = [ + ":d_storage", + "//storage:res_leveldb", + "//common:comm", + ], +) + + +cc_library( + name = "leveldb", + srcs = ["leveldb.cpp"], + hdrs = ["leveldb.h"], + deps = [ + ":data_storage", + "//common:comm", + "//storage:res_leveldb" + ], +) + +cc_library( + name = "global_view", + srcs = ["global_view.cpp"], + hdrs = ["global_view.h"], + deps = [ + ":data_storage", + "//common:comm", + ], +) + +cc_library( + name = "db_view", + srcs = ["db_view.cpp"], + hdrs = ["db_view.h"], + deps = [ + ":concurrency_controller", + ":streaming_e_controller", + "//common:comm", + ], +) + +cc_library( + name = "executor_state", + srcs = ["executor_state.cpp"], + hdrs = ["executor_state.h"], + deps = [ + ":evm_state", + ":utils", + ":db_view", + "//common:comm", + ], +) + +cc_library( + name = "evm_state", + hdrs = ["evm_state.h"], + deps = [ + ":utils", + "//common:comm", + ], +) + +cc_library( + name = "global_state", + srcs = ["global_state.cpp"], + hdrs = ["global_state.h"], + deps = [ + ":evm_state", + ":utils", + ":global_view", + "//common:comm", + ], +) + +cc_library( + name = "concurrency_controller", + srcs = ["concurrency_controller.cpp"], + hdrs = ["concurrency_controller.h"], + deps = [ + ":data_storage", + ], +) + +cc_library( + name = "streaming_e_controller", + srcs = ["streaming_e_controller.cpp"], + hdrs = ["streaming_e_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "e_controller", + srcs = ["e_controller.cpp"], + hdrs = ["e_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "mock_e_controller", + hdrs = ["mock_e_controller.h"], + deps = [ + ":streaming_e_controller", + "//common/test:test" + ], +) + +cc_library( + name = "x_controller", + srcs = ["x_controller.cpp"], + hdrs = ["x_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "fx_controller", + srcs = ["fx_controller.cpp"], + hdrs = ["fx_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "dx_controller", + srcs = ["dx_controller.cpp"], + hdrs = ["dx_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + + +cc_library( + name = "contract_executor", + srcs = ["contract_executor.cpp"], + hdrs = ["contract_executor.h"], + deps = [ + ":evm_state", + ":concurrency_controller", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + "//service/contract/executor/common:contract_execute_info", + ], +) + +cc_library( + name = "committer_context", + srcs = ["committer_context.cpp"], + hdrs = ["committer_context.h"], + deps = [ + ":contract_committer", + ], +) + +cc_library( + name = "contract_committer", + hdrs = ["contract_committer.h"], + deps = [ + ":contract_executor", + "//service/contract/proto:func_params_cc_proto", + "//service/contract/executor/common:contract_execute_info", + ], +) + +cc_library( + name = "streaming_e_committer", + srcs = ["streaming_e_committer.cpp"], + hdrs = ["streaming_e_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":streaming_e_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + #"//platform/common/queue:priority_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "e_committer", + srcs = ["e_committer.cpp"], + hdrs = ["e_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":e_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "seq_committer", + srcs = ["seq_committer.cpp"], + hdrs = ["seq_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + + +cc_test( + name = "e_committer_test", + srcs = ["e_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_data_storage", + ":e_committer", + "//common/test:test_main", + ], +) + +cc_library( + name = "2pl_committer", + srcs = ["2pl_committer.cpp"], + hdrs = ["2pl_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":2pl_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "2pl_controller", + srcs = ["2pl_controller.cpp"], + hdrs = ["2pl_controller.h"], + deps = [ + ":concurrency_controller", + ":d_storage", + "//platform/common/queue:lock_free_queue", + "//common:comm", + "//common/utils:utils", + ], +) + +cc_library( + name = "x_committer", + srcs = ["x_committer.cpp"], + hdrs = ["x_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":x_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "fx_committer", + srcs = ["fx_committer.cpp"], + hdrs = ["fx_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":fx_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "dx_committer", + srcs = ["dx_committer.cpp"], + hdrs = ["dx_committer.h"], + deps = [ + ":contract_committer", + ":contract_executor", + ":executor_state", + ":committer_context", + ":dx_controller", + ":global_state", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "x_committer_test", + srcs = ["x_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_data_storage", + ":x_committer", + "//common/test:test_main", + ], +) + + +cc_test( + name = "streaming_e_committer_test", + srcs = ["streaming_e_committer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + "//service/contract/executor/manager/test_data:kv.json", + ], + deps = [ + ":contract_deployer", + ":address_manager", + ":mock_d_storage", + ":mock_e_controller", + ":streaming_e_committer", + "//common/test:test_main", + ], +) + + +cc_library( + name = "contract_deployer", + srcs = ["contract_deployer.cpp"], + hdrs = ["contract_deployer.h"], + deps = [ + ":concurrency_controller", + ":contract_committer", + ":global_state", + ":address_manager", + ], +) + +cc_test( + name = "contract_deployer_test", + srcs = ["contract_deployer_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":test_committer", + ":contract_deployer", + "//common/test:test_main", + ], +) + +cc_library( + name = "contract_manager", + srcs = ["contract_manager.cpp"], + hdrs = ["contract_manager.h"], + deps = [ + ":contract_deployer", + ":e_committer", + ":x_committer", + ":dx_committer", + ":seq_committer", + ":fx_committer", + ":streaming_e_committer", + ":2pl_committer", + ":x_verifier", + ":global_state", + ":address_manager", + ":utils", + "//common:comm", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_test( + name = "contract_manager_test", + srcs = ["contract_manager_test.cpp"], + data = [ + "//service/contract/executor/manager/test_data:contract.json", + ], + deps = [ + ":contract_manager", + "//common/test:test_main", + ], +) + +cc_library( + name = "contract_verifier", + hdrs = ["contract_verifier.h"], + srcs = ["contract_verifier.cpp"], + deps = [ + ":contract_executor", + ":contract_committer", + ":committer_context", + ":concurrency_controller", + "//service/contract/proto:func_params_cc_proto", + ], +) + +cc_library( + name = "local_view", + srcs = ["local_view.cpp"], + hdrs = ["local_view.h"], + deps = [ + ":concurrency_controller", + "//common:comm", + ], +) + +cc_library( + name = "local_state", + srcs = ["local_state.cpp"], + hdrs = ["local_state.h"], + deps = [ + ":evm_state", + ":utils", + ":local_view", + "//common:comm", + ], +) +cc_library( + name = "v_controller", + srcs = ["v_controller.cpp"], + hdrs = ["v_controller.h"], + deps = [ + ":concurrency_controller", + "//platform/common/queue:lock_free_queue", + "//common:comm", + ], +) + +cc_library( + name = "x_verifier", + srcs = ["x_verifier.cpp"], + hdrs = ["x_verifier.h"], + deps = [ + ":contract_verifier", + ":local_state", + ":global_state", + ":v_controller", + "//common/utils:utils", + "//platform/common/queue:lock_free_queue", + "//service/contract/proto:func_params_cc_proto", + ], +) diff --git a/platform/consensus/ordering/fides/executor/x_manager/address_manager.cpp b/platform/consensus/ordering/fides/executor/x_manager/address_manager.cpp new file mode 100644 index 000000000..874226b30 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/address_manager.cpp @@ -0,0 +1,84 @@ +/* + * 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 "service/contract/executor/x_manager/address_manager.h" + +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +Address AddressManager::CreateRandomAddress() { + std::vector raw(20); + std::generate(raw.begin(), raw.end(), []() { return rand(); }); + Address address = eevm::from_big_endian(raw.data(), raw.size()); + users_.insert(address); + return address; +} + +bool AddressManager::CreateAddress(const Address& address) { + if(users_.find(address) != users_.end()){ + return false; + } + users_.insert(address); + return true; +} + +bool AddressManager::Exist(const Address& address) { + return users_.find(address) != users_.end(); +} + +Address AddressManager::CreateContractAddress(const Address& owner) { + return eevm::generate_address(owner, 0u); +} + +std::string AddressManager::AddressToHex(const Address& address) { + return eevm::to_hex_string(address); +} + +Address AddressManager::HexToAddress(const std::string& address) { + return eevm::to_uint256(address); +} + +uint256_t AddressManager::AddressToSHAKey(const Address& address) { + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + //code[63]=1; + //LOG(ERROR)<<"code size:"<(code.size()), h); + //LOG(ERROR)<<"get h:"< + +#include "service/contract/executor/x_manager/utils.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class AddressManager { + public: + AddressManager() {} + + // Create an address holding a 20 byte value + Address CreateRandomAddress(); + bool CreateAddress(const Address& address); + bool Exist(const Address& address); + + static Address CreateContractAddress(const Address& owner); + + static std::string AddressToHex(const Address& address); + static Address HexToAddress(const std::string& address); + + static uint256_t AddressToSHAKey(const Address& address); + + private: + std::set
users_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/address_manager_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/address_manager_test.cpp new file mode 100644 index 000000000..cb7d7454c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/address_manager_test.cpp @@ -0,0 +1,58 @@ +/* + * 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 "service/contract/executor/manager/address_manager.h" + +#include +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { +namespace { + +TEST(AddressManagerTest, CreateAddress) { + Address address = AddressManager().CreateRandomAddress(); + std::array raw; + eevm::to_big_endian(address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, address); +} + +TEST(AddressManagerTest, CreateContractAddress) { + Address address = AddressManager().CreateRandomAddress(); + + Address contract_address = AddressManager::CreateContractAddress(address); + + std::array raw; + eevm::to_big_endian(contract_address, raw.data()); + Address m = eevm::from_big_endian(raw.data() + 12, raw.size() - 12); + EXPECT_EQ(m, contract_address); +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/committer_context.cpp b/platform/consensus/ordering/fides/executor/x_manager/committer_context.cpp new file mode 100644 index 000000000..764a3a53e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/committer_context.cpp @@ -0,0 +1,69 @@ +/* + * 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 "service/contract/executor/x_manager/committer_context.h" + +#include "glog/logging.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +ExecutionContext::ExecutionContext(const ContractExecuteInfo & info ) { + info_ = std::make_unique(info); +} + +const ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() const { + return info_.get(); +} + +ContractExecuteInfo * ExecutionContext::GetContractExecuteInfo() { + return info_.get(); +} + +void ExecutionContext::SetResult(std::unique_ptr result) { + result_ = std::move(result); +} + +bool ExecutionContext::IsRedo() { + return is_redo_; +} + +void ExecutionContext::SetRedo(){ + is_redo_++; +} + +int ExecutionContext::RedoTime() { + return is_redo_; +} + + +std::unique_ptr ExecutionContext::FetchResult() { + return std::move(result_); +} + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/committer_context.h b/platform/consensus/ordering/fides/executor/x_manager/committer_context.h new file mode 100644 index 000000000..5f868da91 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/committer_context.h @@ -0,0 +1,56 @@ +/* + * 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 "service/contract/executor/x_manager/contract_committer.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ExecutionContext { +public: + ExecutionContext(const ContractExecuteInfo & info ) ; + const ContractExecuteInfo * GetContractExecuteInfo() const; + ContractExecuteInfo * GetContractExecuteInfo(); + + void SetRedo(); + bool IsRedo(); + int RedoTime(); + + void SetResult(std::unique_ptr result); + std::unique_ptr FetchResult(); + + int64_t start_time = 0; + private: + int is_redo_ = 0; + std::unique_ptr result_; + std::unique_ptr info_; +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.cpp new file mode 100644 index 000000000..137ccd7b8 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.cpp @@ -0,0 +1,17 @@ +#include "service/contract/executor/x_manager/concurrency_controller.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +ConcurrencyController::ConcurrencyController(DataStorage * storage) : storage_(storage){} + +const DataStorage * ConcurrencyController::GetStorage() const { + return storage_; +} + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.h b/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.h new file mode 100644 index 000000000..fef2cfa54 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/concurrency_controller.h @@ -0,0 +1,53 @@ +#pragma once + +#include "service/contract/executor/x_manager/data_storage.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +enum State{ + LOAD = 0, + STORE = 1, + REMOVE = 2, +}; + +struct Data{ + State state; + uint256_t data; + int64_t version; + uint256_t old_data; + int commit_version; + bool has_read = false; + bool invalid = false; + Data(){} + Data(const State& state):state(state){} + Data(const State& state, const uint256_t& data, int64_t version = 0) + :state(state), data(data), version(version){} + Data(const State& state, const uint256_t& data, int64_t version, const uint256_t& old_data) + :state(state), data(data), version(version), old_data(old_data){} + bool operator != (const Data& d) const{ + return d.state != this->state || d.data != this->data || d.version != this->version; + } +}; + +class ConcurrencyController { + public: + ConcurrencyController(DataStorage * storage); + + typedef std::map> ModifyMap; + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_) = 0; + + const DataStorage * GetStorage() const; + + protected: + DataStorage * storage_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_committer.h b/platform/consensus/ordering/fides/executor/x_manager/contract_committer.h new file mode 100644 index 000000000..80911cd1a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_committer.h @@ -0,0 +1,80 @@ +/* + * 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 "service/contract/executor/x_manager/contract_executor.h" + +#include "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "eEVM/address.h" + +#include "service/contract/proto/func_params.pb.h" +#include "service/contract/executor/common/contract_execute_info.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +/* +struct ContractExecuteInfo { + eevm::Address caller_address; + eevm::Address contract_address; + std::string func_addr; + Params func_params; + int64_t commit_id; + uint64_t user_id; + ContractExecuteInfo(){} + ContractExecuteInfo( + eevm::Address caller_address, + eevm::Address contract_address, + std::string func_addr, + Params func_params, + int64_t commit_id): caller_address(caller_address), contract_address(contract_address), func_addr(func_addr), func_params(func_params), commit_id(commit_id){} +}; +*/ + +class ContractCommitter { + public: + ContractCommitter() = default; + virtual ~ContractCommitter() = default; + + virtual std::vector> ExecContract(std::vector& request) = 0; + + virtual void AsyncExecContract(std::vector& request){}; + + virtual absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) = 0; + + virtual void SetExecuteCallBack(std::function)> ){}; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.cpp new file mode 100644 index 000000000..8c34d5757 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.cpp @@ -0,0 +1,187 @@ +/* + * 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 "service/contract/executor/x_manager/contract_deployer.h" + +#include "service/contract/executor/x_manager/address_manager.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); + LOG(ERROR)<<"add arg:"<& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +} + +ContractDeployer::ContractDeployer(ContractCommitter * committer, GlobalState * gs) + : committer_(committer), gs_(gs){} + +std::string ContractDeployer::GetFuncAddress(const Address& contract_address, + const std::string& func_name) { + //LOG(ERROR)<<" deployer:"<create(contract_address, 0u, contract_constructor); + absl::StatusOr result = committer_->ExecContract( + owner_address, contract_address, + "", + {}, gs_); + + if(result.ok()){ + // set the initialized class context code. + contract.acc.set_code(eevm::to_bytes(*result)); + + for (const auto& info : deploy_info.func_info()) { + SetFuncAddress(contract_address, info); + } + return contract.acc.get_address(); + } else { + gs_->remove(contract_address); + return 0; + } + } catch (...) { + LOG(ERROR) << "Deploy throw expection"; + return 0; + } +} + +bool ContractDeployer::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, const Address& contract_address) { + LOG(ERROR)<<"deploy address:"<create(contract_address, 0u, contract_constructor); + + LOG(ERROR)<<"create:"; + absl::StatusOr result = committer_->ExecContract( + owner_address, contract_address, + "", + {}, gs_); + + LOG(ERROR)<<"deploy result:"<remove(contract_address); + return false; + } + } catch (...) { + LOG(ERROR) << "Deploy throw expection"; + return false; + } +} + +Address ContractDeployer::DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params){ + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json["bin"]); + + for (auto& func : contract_json["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + for(const uint256_t& param : init_params){ + deploy_info.add_init_param(U256ToString(param)); + } + + return DeployContract(owner_address, deploy_info); +} + +bool ContractDeployer::DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params, const Address& contract_address){ + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json["bin"]); + + for (auto& func : contract_json["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + for(const uint256_t& param : init_params){ + deploy_info.add_init_param(U256ToString(param)); + } + + return DeployContract(owner_address, deploy_info, contract_address); +} + +absl::StatusOr ContractDeployer::GetContract( + const Address& address) { + if (!gs_->Exists(address)) { + return absl::InvalidArgumentError("Contract not exist."); + } + + return gs_->get(address); +} + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.h b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.h new file mode 100644 index 000000000..84f100360 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer.h @@ -0,0 +1,66 @@ +/* + * 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 "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ContractDeployer { + public: + ContractDeployer(ContractCommitter * committer, GlobalState * gs); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + bool DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, const Address& contract_address); + + Address DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params); + bool DeployContract(const Address& owner_address, + const nlohmann::json& contract_json, const std::vector& init_params, const Address& contract_address); + + absl::StatusOr GetContract(const Address& address); + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + + private: + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + ContractCommitter* committer_; + GlobalState* gs_; + std::map> func_address_; +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_deployer_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer_test.cpp new file mode 100644 index 000000000..41be4ae2c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_deployer_test.cpp @@ -0,0 +1,124 @@ +/* + * 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 "service/contract/executor/manager/contract_deployer.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" +#include "service/contract/executor/manager/test_committer.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } + +class ContractDeployerTest : public Test { + public: + ContractDeployerTest() : owner_address_(get_random_address()) { + + storage_ = std::make_unique(); + gs_ = std::make_unique(storage_.get()); + execotor_ = std::make_unique(storage_.get(), gs_.get()); + + + + LOG(ERROR)<<"owner:"< storage_; + std::unique_ptr gs_; + std::unique_ptrexecotor_; +}; + +TEST_F(ContractDeployerTest, NoContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + auto account = deployer.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContract) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + deployer.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractDeployerTest, DeployContractFromJson) { + ContractDeployer deployer(execotor_.get(), gs_.get()); + + Address contract_address = + deployer.DeployContract(owner_address_, contract_json_, {1000}); + EXPECT_GT(contract_address, 0); + auto account = deployer.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_executor.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_executor.cpp new file mode 100644 index 000000000..2346d7f67 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_executor.cpp @@ -0,0 +1,103 @@ +/* + * 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 "service/contract/executor/x_manager/contract_executor.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +void AppendArgToInput(std::vector& code, const uint256_t& arg) { + const auto pre_size = code.size(); + code.resize(pre_size + 32u); + eevm::to_big_endian(arg, code.data() + pre_size); +} + +void AppendArgToInput(std::vector& code, const std::string& arg) { + AppendArgToInput(code, eevm::to_uint256(arg)); +} + +absl::StatusOr ContractExecutor::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + + std::vector inputs; + if(!func_addr.empty()){ + inputs = eevm::to_bytes(func_addr); + } + for (const std::string& param : func_param.param()) { + AppendArgToInput(inputs, param); + } + + auto result = Execute(caller_address, contract_address, inputs, state); + + if (result.ok()) { + return eevm::to_hex_string(*result); + } + else { + //LOG(ERROR)<<"execute fail:"<> ContractExecutor::Execute( + const Address& caller_address, const Address& contract_address, + const std::vector& input, + EVMState * state) { + // Ignore any logs produced by this transaction + eevm::NullLogHandler ignore; + eevm::Transaction tx(caller_address, ignore); + + + // Record a trace to aid debugging + eevm::Trace tr; + eevm::Processor p(*state); + + // Run the transaction + try { + const auto exec_result = + p.run(tx, caller_address, state->get(contract_address), input, 0u, &tr); + + if (exec_result.er != eevm::ExitReason::returned) { + // Print the trace if nothing was returned + if (exec_result.er == eevm::ExitReason::threw) { + return absl::InternalError( + fmt::format("Execution error: {}", exec_result.exmsg)); + } + return absl::InternalError("Deployment did not return"); + } + return exec_result.output; + } catch (...) { + return absl::InternalError(fmt::format("Execution error:")); + } +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_executor.h b/platform/consensus/ordering/fides/executor/x_manager/contract_executor.h new file mode 100644 index 000000000..6962c7b1e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_executor.h @@ -0,0 +1,74 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/executor/x_manager/evm_state.h" +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/common/contract_execute_info.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +/* +struct ExecuteResp { + int ret; + absl::Status state; + int64_t commit_id; + Address contract_address; + ConcurrencyController :: ModifyMap rws; + std::string result; + int retry_time = 0; + uint64_t user_id = 0; + double runtime = 0; +}; +*/ + +class ContractExecutor { + public: + ContractExecutor() = default; + + ~ContractExecutor() = default; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + absl::StatusOr> Execute( + const Address& owner_address, const Address& contract_address, + const std::vector& func_params, EVMState * state); +}; + +} // namespace contract +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_executor_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_executor_test.cpp new file mode 100644 index 000000000..3c14e08c5 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_executor_test.cpp @@ -0,0 +1,367 @@ +/* + * 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 "service/contract/executor/manager/multi_contract_executor.h" +#include "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +uint256_t GetAddressHash(uint256_t address){ + std::vectorcode; + code.resize(64u); + eevm::to_big_endian(address, code.data()); + code[63]=1; + + uint8_t h[32]; + eevm::keccak_256(code.data(), static_cast(code.size()), h); + return eevm::from_big_endian(h, sizeof(h)); +} + + +class MultiContractExecutorTest : public Test { + public: + MultiContractExecutorTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + LOG(ERROR)<<"test dir:"< storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + LOG(ERROR)<<"owner address:"<Load(owner_key).first, 1000); + } + +/* + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 0); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 300); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + */ +} +/* + +TEST_F(MultiContractExecutorTest, ExecMultiContractNoConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 400); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + info.push_back(ContractExecuteInfo(transfer_receiver, contract_address, "", func_params, 0)); + } + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 200); + EXPECT_EQ(storage_ptr->Load(transfer3_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} + +TEST_F(MultiContractExecutorTest, ExecMultiContractHaveConflict) { + + std::unique_ptr storage = std::make_unique(); + DataStorage * storage_ptr = storage.get(); + ContractManager manager(std::move(storage)); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + uint256_t owner_key = GetAddressHash(owner_address_); + uint256_t transfer_key = GetAddressHash(transfer_receiver); + uint256_t transfer2_key = GetAddressHash(transfer_receiver2); + uint256_t transfer3_key = GetAddressHash(transfer_receiver3); + + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address, "", func_params, 0)); + } + + std::vector> resp = manager.ExecContract(info); + EXPECT_EQ(storage_ptr->Load(owner_key).first, 800); + EXPECT_EQ(storage_ptr->Load(transfer_key).first, 100); + EXPECT_EQ(storage_ptr->Load(transfer2_key).first, 100); + + LOG(ERROR)<<"resp size:"<ret, 0); + EXPECT_EQ(resp[1]->ret, 0); + + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(HexToInt(resp[1]->result), 1); +} +*/ + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_manager.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_manager.cpp new file mode 100644 index 000000000..6a8873eb0 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_manager.cpp @@ -0,0 +1,166 @@ +/* + * 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 "service/contract/executor/x_manager/contract_manager.h" + +#include "service/contract/executor/x_manager/address_manager.h" +#include "service/contract/executor/x_manager/streaming_e_committer.h" +#include "service/contract/executor/x_manager/e_committer.h" +#include "service/contract/executor/x_manager/x_committer.h" +#include "service/contract/executor/x_manager/dx_committer.h" +#include "service/contract/executor/x_manager/fx_committer.h" +#include "service/contract/executor/x_manager/2pl_committer.h" +#include "service/contract/executor/x_manager/seq_committer.h" +#include "service/contract/executor/x_manager/x_verifier.h" + +#include +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +ContractManager::ContractManager(std::unique_ptr storage, + int worker_num, Options op){ + storage_ = std::move(storage); + gs_ = std::make_unique(storage_.get()); + + if(op == Streaming){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, nullptr, worker_num); + } + else if(op == TwoPL){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + else if(op == X){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + else if(op == FX){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + else if(op == SEQ){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + else if(op == DX){ + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + else { + committer_ = std::make_unique(storage_.get(), gs_.get(), 1500, worker_num); + } + + deployer_ = std::make_unique(committer_.get(), gs_.get()); + verifier_ = std::make_unique(storage_.get(), gs_.get(), worker_num); +} + +void ContractManager::SetExecuteCallBack(std::function resp)> func) { + committer_->SetExecuteCallBack(std::move(func)); +} + +Address ContractManager::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info) { + return deployer_->DeployContract(owner_address, deploy_info); +} + +bool ContractManager::DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, + const Address& contract_address) { + return deployer_->DeployContract(owner_address, deploy_info, contract_address); +} + +absl::StatusOr ContractManager::GetContract(const Address& address) { + return deployer_->GetContract(address); +} + +std::vector> ContractManager::ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); +} + +void ContractManager::AsyncExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name()<<" idx:"<AsyncExecContract(execute_info); +} + + +absl::StatusOr ContractManager::ExecContract( + const Address& caller_address, const Address& contract_address, + const Params& func_param) { + std::string func_addr = + deployer_->GetFuncAddress(contract_address, func_param.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << func_param.func_name(); + return absl::InvalidArgumentError("Func not exist."); + } + + absl::StatusOr result = committer_->ExecContract( + caller_address, contract_address, + func_addr, + func_param, gs_.get()); + if(result.ok()){ + return *result; + } + return result.status(); +} + +bool ContractManager::VerifyContract( + std::vector& ordered_info, + std::vector rws_list) { + return true; + for(int i = 0; i < ordered_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(ordered_info[i].contract_address, ordered_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << ordered_info[i].func_params.func_name(); + ordered_info[i].contract_address = 0; + continue; + } + ordered_info[i].func_addr = func_addr; + } + return verifier_->VerifyContract(ordered_info, rws_list); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_manager.h b/platform/consensus/ordering/fides/executor/x_manager/contract_manager.h new file mode 100644 index 000000000..e16ef015d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_manager.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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_deployer.h" +#include "service/contract/executor/x_manager/contract_verifier.h" +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ContractManager { + public: + enum Options { + Streaming = 1, + None = 2, + X = 3, + TwoPL = 4, + FX = 5, + SEQ = 6, + DX = 7, + }; + ContractManager(std::unique_ptr storage, + int worker_num = 2, Options op = Streaming ); + + public: + Address DeployContract(const Address& owner_address, + const DeployInfo& deploy_info); + + bool DeployContract(const Address& owner_address, + const DeployInfo& deploy_info, + const Address& contract_address); + + absl::StatusOr GetContract(const Address& address); + + absl::StatusOr ExecContract(const Address& caller_address, + const Address& contract_address, + const Params& func_param); + + std::vector> ExecContract(std::vector& execute_info); + + void AsyncExecContract(std::vector& execute_info); + + void SetExecuteCallBack(std::function resp)> func); + + bool VerifyContract( + std::vector& ordered_info, + std::vector rws_list); + private: + std::string GetFuncAddress(const Address& contract_address, + const std::string& func_name); + void SetFuncAddress(const Address& contract_address, const FuncInfo& func); + + private: + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptr committer_; + std::unique_ptr deployer_; + std::unique_ptr verifier_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_manager_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_manager_test.cpp new file mode 100644 index 000000000..759a24666 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_manager_test.cpp @@ -0,0 +1,259 @@ +/* + * 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 "service/contract/executor/manager/contract_manager.h" + +#include +#include + +#include + +#include "service/contract/executor/manager/address_manager.h" + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class ContractManagerTest : public Test { + public: + ContractManagerTest() : owner_address_(get_random_address()) { + LOG(ERROR)<<"owner:"<()); + auto account = manager.GetContract(1234); + EXPECT_FALSE(account.ok()); +} + +TEST_F(ContractManagerTest, DeployContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); +} + +TEST_F(ContractManagerTest, InitContract) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + LOG(ERROR)<<" deploy done:"<()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1000); + } + + // receiver 0 + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 0); + } + + // transfer 400 to receiver + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(400)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + + // receiver 400 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 400); + } + // owner 600 + { + Address caller = get_random_address(); + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = manager.ExecContract(caller, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 600); + } + + // transfer 200 to receiver2 from receiver + { + Params func_params; + func_params.set_func_name("approve(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } + { + Params func_params; + func_params.set_func_name("transferFrom(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(200)); + + auto result = + manager.ExecContract(transfer_receiver, contract_address, func_params); + EXPECT_EQ(HexToInt(*result), 1); + } +} + +TEST_F(ContractManagerTest, NoFunc) { + ContractManager manager(std::make_unique()); + + DeployInfo deploy_info; + deploy_info.set_contract_bin(contract_json_["bin"]); + for (auto& func : contract_json_["hashes"].items()) { + FuncInfo* new_func = deploy_info.add_func_info(); + new_func->set_func_name(func.key()); + new_func->set_hash(func.value()); + } + + deploy_info.add_init_param(U256ToString(1000)); + + Address contract_address = + manager.DeployContract(owner_address_, deploy_info); + EXPECT_GT(contract_address, 0); + auto account = manager.GetContract(contract_address); + EXPECT_TRUE(account.ok()); + + // owner 1000 + { + Params func_params; + func_params.set_func_name("balanceOf()"); + func_params.add_param(U256ToString(owner_address_)); + + auto result = + manager.ExecContract(owner_address_, contract_address, func_params); + EXPECT_FALSE(result.ok()); + } +} + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.cpp b/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.cpp new file mode 100644 index 000000000..8400a4f8a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.cpp @@ -0,0 +1,54 @@ +/* + * 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 "service/contract/executor/x_manager/contract_verifier.h" + +namespace resdb { +namespace contract { +namespace x_manager { + + +ContractVerifier:: ContractVerifier(){ + executor_ = std::make_unique(); +} + +ContractVerifier::~ContractVerifier(){ +} + +std::vector> ContractVerifier::ExecContract(std::vector& request ){ + return std::vector>(); +} + +absl::StatusOr ContractVerifier::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb + diff --git a/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.h b/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.h new file mode 100644 index 000000000..0c11c997c --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/contract_verifier.h @@ -0,0 +1,61 @@ +/* + * 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 "absl/status/statusor.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/contract_executor.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ContractVerifier : public ContractCommitter { + public: + ContractVerifier(); + virtual ~ContractVerifier(); + + virtual bool VerifyContract( + const std::vector& request_list, + const std::vector& rws_list) = 0; + + std::vector> ExecContract( + std::vector& request )override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) override; + + protected: + std::unique_ptr executor_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/d_storage.cpp b/platform/consensus/ordering/fides/executor/x_manager/d_storage.cpp new file mode 100644 index 000000000..09a27bbc8 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/d_storage.cpp @@ -0,0 +1,173 @@ +#include "service/contract/executor/x_manager/d_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1024; + } + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + return v+1; +} + +std::pair InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return std::make_pair(0,0); + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + return 0; + } + return it->second.second; +} + +} + +int64_t D_Storage::StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local) { +//LOG(ERROR)<<"storage key:"< D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"<second.second)<<" key:"< +#include + +#include "service/contract/executor/x_manager/data_storage.h" + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class D_Storage : public DataStorage { + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual int64_t StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_[1024]; + mutable std::shared_mutex mutex_[1024]; + + std::map > g_s_[1024]; + mutable std::shared_mutex g_mutex_[1024]; + +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/data_storage.cpp b/platform/consensus/ordering/fides/executor/x_manager/data_storage.cpp new file mode 100644 index 000000000..4b0f0b632 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/data_storage.cpp @@ -0,0 +1,81 @@ +#include "service/contract/executor/x_manager/data_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1024; + } +} + +int64_t DataStorage::Store(const uint256_t& key, const uint256_t& value, bool) { +int idx = GetHashKey(key); + + std::unique_lock lock(mutex_[idx]); + //LOG(ERROR)<<"store key:"< DataStorage::Load(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + //LOG(ERROR)<<"load key:"<second; +} + +bool DataStorage::Remove(const uint256_t& key, bool) { +int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + auto e = s[idx].find(key); + if (e == s[idx].end()) + return false; + s[idx].erase(e); + return true; +} + +bool DataStorage::Exist(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + return s[idx].find(key) != s[idx].end(); +} + +int64_t DataStorage::GetVersion(const uint256_t& key, bool) const{ +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + auto it = s[idx].find(key); + if( it == s[idx].end()){ + return 0; + } + return it->second.second; +} + +int64_t DataStorage::StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool ) { +int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + s[idx][key] = std::make_pair(value,version); + return 0; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/data_storage.h b/platform/consensus/ordering/fides/executor/x_manager/data_storage.h new file mode 100644 index 000000000..de68345e0 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/data_storage.h @@ -0,0 +1,34 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" + +namespace resdb { +namespace contract { + +class DataStorage { + +public: + DataStorage() = default; + virtual ~DataStorage() = default; + + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local = false); + virtual std::pair Load(const uint256_t& key, bool is_local_view = false) const; + virtual bool Remove(const uint256_t& key, bool is_local = false); + virtual bool Exist(const uint256_t& key, bool is_local = false) const; + virtual int64_t StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local = false); + + virtual int64_t GetVersion(const uint256_t& key, bool is_local = false) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local = false) {} + virtual void Flush(){}; + +protected: + std::map > s[4096]; + mutable std::shared_mutex mutex_[4096]; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/db_view.cpp b/platform/consensus/ordering/fides/executor/x_manager/db_view.cpp new file mode 100644 index 000000000..dac5612da --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/db_view.cpp @@ -0,0 +1,42 @@ +#include "service/contract/executor/x_manager/db_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +DBView::DBView(ConcurrencyController * controller, int64_t commit_id, int version) + :controller_(static_cast(controller)), + commit_id_(commit_id), version_(version){ + } + +void DBView::store(const uint256_t& key, const uint256_t& value) { + //LOG(ERROR)<<"store:"<Store(commit_id_, key, value, version_); +} + +uint256_t DBView::load(const uint256_t& key) { + //LOG(ERROR)<<"laod:"<Load(commit_id_, key, version_); +} + +bool DBView::remove(const uint256_t& key) { + //LOG(ERROR)<<"remove key:"<Remove(commit_id_, key, version_); +} + +/* +void DBView::Flesh(int64_t commit_id) { + commit_id_ = commit_id; + //LOG(ERROR)<<"commit push:"<PushCommit(commit_id, local_changes_); + local_changes_.clear(); +} +*/ + + +}} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/db_view.h b/platform/consensus/ordering/fides/executor/x_manager/db_view.h new file mode 100644 index 000000000..7eac21da5 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/db_view.h @@ -0,0 +1,42 @@ +#pragma once + +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class DBView : public eevm::Storage { + +public: + DBView(ConcurrencyController * controller, int64_t commit_id, int version); + virtual ~DBView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + + // for 2PL, once it is done, all the commit will be pushed to + // the controller to judge if it can be committed. + // During the flesh, all the changes will be removed. + void Flesh(int64_t commit_id) {} + // Commit the changes. If there is a conflict, return false. + // Make sure all other committers have pushed their changes before calling Commit. + //bool Commit(); + // Remove all the changes. + //void Abort(); + +private: + StreamingEController * controller_; + int64_t commit_id_; + int version_; + std::map> local_changes_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/dx_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/dx_committer.cpp new file mode 100644 index 000000000..cb1ebc363 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/dx_committer.cpp @@ -0,0 +1,272 @@ +/* + * 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 "service/contract/executor/x_manager/dx_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +DXCommitter:: DXCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + //LOG(ERROR)<<"init window:"<(storage, window_size); + //controller_ = std::make_unique(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + //context_list_.resize(window_size_); + //tmp_resp_list_.resize(window_size_); + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + resp->runtime = track.GetRunTime()*1000; + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + + +DXCommitter::~DXCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void DXCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +ExecutionContext* DXCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void DXCommitter::AsyncExecContract(std::vector& requests) { + + return ; +} + +std::vector> DXCommitter::ExecContract( + std::vector& requests) { + + controller_->Clear(); + int id = 1; + int window = 200; + + std::vector> tmp_resp_list; + std::queue> wq; + + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + context_ptr->start_time = GetCurrentTime(); + + AddTask(request.commit_id, std::move(context)); + if(window == 0){ + wq.push(std::make_unique(context_ptr)); + } + else { + request_queue_.Push(std::make_unique(context_ptr)); + window--; + } + tmp_resp_list.push_back(nullptr); + } + + tmp_resp_list.push_back(nullptr); + std::vector> resp_list; + + int process_num = id-1; + //LOG(ERROR)<<"wait num:"<0) { + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + + int64_t resp_id= resp->commit_id; + int ret = resp->ret; + #ifdef Debug + LOG(ERROR)<<"resp:"<Commit(resp_id); + if(!commit_ret){ + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + const auto& redo_list = controller_->GetRedo(); + for(int64_t id : redo_list) { + controller_->Clear(id); + auto context_ptr = GetTaskContext(id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + + const auto& done_list = controller_->GetDone(); + for(int id : done_list){ + //tmp_resp_list[id]->rws = *controller_->GetChangeList(id); + tmp_resp_list[id]->delay = controller_->GetDelay(id)/1000.0; + //LOG(ERROR)<<"done :"<rws.size(); + resp_list.push_back(std::move(tmp_resp_list[id])); + process_num--; + if(!wq.empty()){ + auto nt = std::move(wq.front()); + wq.pop(); + request_queue_.Push(std::move(nt)); + } + continue; + } + //resp_list.push_back(std::move(tmp_resp_list[resp_id])); + //process_num--; + //continue; + } + else { + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + return resp_list; +// LOG(ERROR)<<"last id:"< DXCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/dx_committer.h b/platform/consensus/ordering/fides/executor/x_manager/dx_committer.h new file mode 100644 index 000000000..7f530d195 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/dx_committer.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/dx_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class DXCommitter : public ContractCommitter { + public: + DXCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~DXCommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + //std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/dx_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/dx_controller.cpp new file mode 100644 index 000000000..b290aa927 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/dx_controller.cpp @@ -0,0 +1,1759 @@ +#include "service/contract/executor/x_manager/dx_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } + } + + +DXController::DXController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< DXController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void DXController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int DXController::GetLastR(const uint256_t& address, int addr_idx){ + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< DXController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set DXController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + if(!new_cf.empty()){ + std::set r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + } + } + + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect :"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int DXController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool DXController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + //LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + /* + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + //LOG(ERROR)<<" commit id done:"< &DXController::GetRedo(){ + return redo_; +} +const std::vector &DXController::GetDone(){ + return done_; +} + +uint64_t DXController::GetDelay(int64_t commit_id) { + return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/dx_controller.h b/platform/consensus/ordering/fides/executor/x_manager/dx_controller.h new file mode 100644 index 000000000..88b636f32 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/dx_controller.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class DXController : public ConcurrencyController { + public: + DXController(DataStorage * storage, int window_size); + virtual ~DXController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + std::unique_ptr GetChangeList(int64_t commit_id); + + void Clear(); +void Clear(int64_t commit_id); + uint64_t GetDelay(int64_t commit_id); + + private: + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + int GetLastR(const uint256_t& address, int addr_id); + int GetLastW(const uint256_t& address, int addr_id); + int GetLastWOnly(const uint256_t& address, int addr_id); + int FindW(int64_t commit_id, const uint256_t& address, int addr_id); + int FindR(int64_t commit_id, const uint256_t& address, int addr_id); + + bool Connect(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + void ConnectSelf(int idx, int last_idx, const uint256_t& address, int addr_idx); + bool AddNewNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + bool AddOldNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + void AddDataToChangeList(std::vector& change_set, const Data& data); + + void RecursiveAbort(int64_t idx, const uint256_t& address); + void AbortNodeFrom(int64_t idx, const uint256_t& address); + void AbortNode(int64_t idx); + bool ContainRead(int commit_id, const uint256_t &address); + + void RemoveNode(int64_t commit_id); + + void Abort(const int64_t commit_id); + bool Reach(int from, int to); + bool PrintReach(int from, int to); + std::set GetReach(int from, int to); + std::set GetReach(int from); + + int GetDep(int from, int to); + void AddRedo(int64_t commit_id); + + int AddressToId(const uint256_t& key); + uint256_t& GetAddress(int key); + + void ResetReach(int64_t commit_id); +void Update(const std::vector& commit_id) ; + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo():type(0){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + //typedef std::map>> KeyMap; + std::vector changes_list_; + std::vector> addr_changes_list_; + //std::vector> changes_list_; + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, k_mutex_[2048]; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + bool has_write_[2048]; + std::vector abort_list_; + std::set pre_[2048]; + std::set child_[2048]; + std::bitset<2048> reach_[2048]; + + std::map> addr_pre_[2048]; + std::map> addr_child_[2048]; + std::map> root_addr_[2048]; + std::unordered_map > lastr_, lastw_; + + std::unordered_map> check_; + std::set check_abort_; + std::vector post_abort_, pending_check_; + std::map key_[2048]; + std::unordered_map akey_; + std::atomic key_id_; + bool ds_ = false; + uint64_t commit_time_[2048], commit_delay_[2048]; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/e_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/e_committer.cpp new file mode 100644 index 000000000..075a12b4b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/e_committer.cpp @@ -0,0 +1,259 @@ +/* + * 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 "service/contract/executor/x_manager/e_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +ECommitter:: ECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + //LOG(ERROR)<<"init window:"<(storage, window_size); + //controller_ = std::make_unique(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + //context_list_.resize(window_size_); + //tmp_resp_list_.resize(window_size_); + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + resp->runtime = track.GetRunTime()*1000; + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + + +ECommitter::~ECommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void ECommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +ExecutionContext* ECommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void ECommitter::AsyncExecContract(std::vector& requests) { + + return ; +} + +int num = 0; +std::vector> ECommitter::ExecContract( + std::vector& requests) { + + controller_->Clear(); + int id = 0; + std::vector> tmp_resp_list; + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + context_ptr->start_time = GetCurrentTime(); + + AddTask(request.commit_id, std::move(context)); + request_queue_.Push(std::make_unique(context_ptr)); + tmp_resp_list.push_back(nullptr); + } + + std::vector> resp_list; + + int process_num = id; + //LOG(ERROR)<<"wait num:"<0) { + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + + int64_t resp_id= resp->commit_id; + int ret = resp->ret; + #ifdef Debug + LOG(ERROR)<<"resp:"<Commit(resp_id); + if(!commit_ret){ + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + const auto& redo_list = controller_->GetRedo(); + for(int64_t id : redo_list) { + controller_->Clear(id); + auto context_ptr = GetTaskContext(id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + + const auto& done_list = controller_->GetDone(); + for(int id : done_list){ + //tmp_resp_list[id]->rws = *controller_->GetChangeList(id); + tmp_resp_list[id]->delay = controller_->GetDelay(id)/1000.0; + //LOG(ERROR)<<"get delay:"<delay<<" runtime:"<runtime; + //LOG(ERROR)<<"done :"<rws.size(); + resp_list.push_back(std::move(tmp_resp_list[id])); + process_num--; + continue; + } + //resp_list.push_back(std::move(tmp_resp_list[resp_id])); + //process_num--; + //continue; + } + else { + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + return resp_list; +// LOG(ERROR)<<"last id:"< ECommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/e_committer.h b/platform/consensus/ordering/fides/executor/x_manager/e_committer.h new file mode 100644 index 000000000..f0fa8aa2a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/e_committer.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/e_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class ECommitter : public ContractCommitter { + public: + ECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~ECommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + //std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/e_committer_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/e_committer_test.cpp new file mode 100644 index 000000000..8d102270b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/e_committer_test.cpp @@ -0,0 +1,283 @@ +/* + * 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 "service/contract/executor/x_manager/e_committer.h" + +#include + +#include "service/contract/executor/x_manager/mock_data_storage.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_deployer.h" +#include "service/contract/executor/x_manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + std::string(getenv("TEST_WORKSPACE")) + + "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class ECommitterTest : public Test { + public: + ECommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/contract.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["ERC20.sol:ERC20Token"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool) { + LOG(ERROR)<<"load:"<(storage_.get()); + + committer_ = std::make_unique(storage_.get(), gs_.get(), 1024); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + std::vector> ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + return committer_->ExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + std::map> data_; + std::unique_ptr deployer_; +}; + +TEST_F(ECommitterTest, ExecContract) { + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 1); + EXPECT_EQ(resp[0]->ret, 0); + } +} + +// get a +// a->b +TEST_F(ECommitterTest, TwoTxnNoConflict) { + Address transfer_receiver = get_random_address(); + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(transfer_receiver)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + + for(int i = 0; i < 2; ++i){ + EXPECT_EQ(resp[i]->ret, 0); + if(resp[i]->commit_id == 0){ + EXPECT_EQ(HexToInt(resp[i]->result), 1000); + } + else { + EXPECT_EQ(HexToInt(resp[i]->result), 0); + } + } + } +} + +TEST_F(ECommitterTest, TwoTxnConflict) { + Address transfer_receiver = get_random_address(); + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("balanceOf(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + for(int i = 0; i < 2; ++i){ + if(resp[i]->commit_id == 1){ + EXPECT_EQ(resp[i]->ret, 0); + EXPECT_EQ(HexToInt(resp[i]->result), 1); + } + } + } +} + +/* +// a->b +TEST_F(ECommitterTest, TwoTxnConflict1) { + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address) { + if(start){ + load_time[address]++; + } + return data_[address]; + })); + + EXPECT_CALL(*storage_, Store).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value,bool) { + if(start) { + bool done = false; + while(!done){ + for(auto it : load_time){ + if(it.second>1){ + done = true; + break; + } + } + } + } + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + })); + + Init(); + start = true; + + // owner 1000 + std::vector info; + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + std::vector> resp = ExecContract(info); + EXPECT_EQ(resp.size(), 2); + EXPECT_EQ(resp[0]->ret, 0); + EXPECT_EQ(HexToInt(resp[0]->result), 1); + EXPECT_EQ(resp[1]->ret, 0); + EXPECT_EQ(HexToInt(resp[1]->result), 1); + } + //EXPECT_EQ(committer_->GetExecutionState()->commit_time,2); + //EXPECT_EQ(committer_->GetExecutionState()->redo_time,1); +} +*/ + +} // namespace +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/e_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/e_controller.cpp new file mode 100644 index 000000000..f9392f3d0 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/e_controller.cpp @@ -0,0 +1,685 @@ +#include "service/contract/executor/x_manager/e_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + + int GetHashKey(const uint256_t& address){ + return 0; + // Get big-endian form + /* + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + */ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1; + } +} +EController::EController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< lk(abort_mutex_); + //LOG(ERROR)<<"abort:"< EController::FetchAbort(){ + std::vector p; + { + std::lock_guard lk(abort_mutex_); + p = abort_list_; + abort_list_.clear(); + } + return p; +} + +void EController::Clear(int64_t commit_id){ +#ifdef CDebug + //LOG(ERROR)<<"CLEAR id:"< lk(mutex_[hash_idx]); + std::unique_lock lock(g_mutex_); + //std::unique_lock lock(mutex_[hash_idx]); + + + auto& commit_set = pre_commit_list_[address]; + #ifdef CDebug + LOG(ERROR)<<"append commit set:"<> tmp; + while(!commit_set.empty()){ + auto& it = commit_set.back(); + if( !committed_[it->commit_id] && (it->commit_id > commit_id|| it->version==-1)){ + //LOG(ERROR)<<"get data front address:"<commit_id<<" committed:"<commit_id]; + //LOG(ERROR)<<"push :"<commit_id<<" version:"<version; + if(it->commit_id != commit_id){ + tmp.push(std::move(it)); + } + commit_set.pop_back(); + } + else { + break; + } + } + + + if(data.state == LOAD){ + if(commit_set.empty()){ + auto cache_it = cache_data_.find(address); + if(cache_it == cache_data_.end()){ + auto ret = storage_->Load(address, false); + data.data = ret.first; + data.version = ret.second; + //LOG(ERROR)<<"get from db commit id:"<second.first; + data.version = cache_it->second.second; + } + #ifdef CDebug + LOG(ERROR)<<"LOAD from db:"<<" version:"<data.data; + data.version = it->data.version; + #ifdef CDebug + LOG(ERROR)<<"LOAD from :"<commit_id<<" version:"<< it->data.version<<" address:"<version; + #endif + } + } + else { + if(commit_set.empty()){ + } + else { + auto& it = commit_set.back(); + data.version = it->data.version+1; + #ifdef CDebug + LOG(ERROR)<<"LOAD from :"<commit_id<<" version:"<data.version<<" address:"<commit_id == commit_id){ + it->type |= type; + it->data = data; + //assert(it->version==0); + } + else { + commit_set.push_back(std::make_unique(commit_id, type, data)); + commit_set.back()->version = 0; + } + //LOG(ERROR)<<"back type:"<type; + } + else { + commit_set.push_back(std::make_unique(commit_id, type, data)); + commit_set.back()->version = 0; + } + + bool flag = true; + while(!tmp.empty()){ + int ok = 1; + if(data.state == STORE && flag){ + if(tmp.top()->type & 1 ){ +#ifdef CDebug + LOG(ERROR)<<"abbort:"<commit_id<<" from:"<commit_id); + } + else if(tmp.top()->type&2){ + flag = false; + } + } + if(ok){ + commit_set.push_back(std::move(tmp.top())); + } + tmp.pop(); + } + //LOG(ERROR)<<"add size:"< lk(mutex_[hash_idx]); + //std::unique_lock lock(g_mutex_); + //std::unique_lock lock(mutex_[hash_idx]); + + + auto& commit_set = pre_commit_list_[address]; + //LOG(ERROR)<<"remove commit set:"<> tmp; + while(!commit_set.empty()){ + auto& it = commit_set.back(); + if(it->commit_id != commit_id){ + //LOG(ERROR)<<"push queue:"<commit_id<<" address:"<commit_id == commit_id){ + type = it->type; + //LOG(ERROR)<<"remove old data:"<type&1){ + tmp.top()->version=-1; + #ifdef CDebug + LOG(ERROR)<<"abort :"<commit_id<<" from release:"<commit_id); + } + else if((tmp.top()->type&2)){ + flag = false; + } + } + if(ok){ + commit_set.push_back(std::move(tmp.top())); + } + tmp.pop(); + } + //LOG(ERROR)<<"add size:"<GetVersion(it.first, false); + if(op.state == STORE){ + if(op.version+1 != v){ + LOG(ERROR)<<"state:"< lock_index; + //std::set locked; + std::vector >> tmp_data; + //(std::make_pair(op.data, op.version)); + { + std::unique_lock lock1(g_mutex_); + //std::vector>> lock_list; + /* + for(const auto& it : change_set){ + const auto& address = it.first; + int hash_idx = GetHashKey(address); + lock_index.push_back(hash_idx); + } + */ + + for(const auto& it : change_set){ + const auto& address = it.first; + auto& commit_set = pre_commit_list_[address]; + std::unique_ptr last_commit = nullptr; + while(!commit_set.empty()){ + auto &it = commit_set.front(); + if(committed_[it->commit_id]){ + last_commit = std::move(commit_set.front()); + commit_set.pop_front(); + } + else { + break; + } + } + assert(!commit_set.empty()); + /* + if(last_commit){ + LOG(ERROR)<<"get last commit:"<commit_id<<" commit id:"<commit_id != commit_id){ + wait_[commit_id] = true; +#ifdef CDebug + if(commit_set.empty()){ + LOG(ERROR)<<"not the first one: empty:"<commit_id<<" wait for:"<commit_id]<<" address:"<commit_id]){ + commit_set.pop_front(); + } + else { + break; + } + } + std::unique_ptr last_commit = std::move(commit_set.front()); + commit_set.pop_front(); + if(commit_set.empty()){ + } + else { + //LOG(ERROR)<<" commit id:"<commit_id<<" wait:"<commit_id]; + assert(commit_set.front()->commit_id != commit_id); + if(wait_[commit_set.front()->commit_id]){ + pd_.insert(commit_set.front()->commit_id); + } + } + commit_set.push_front(std::move(last_commit)); + } + committed_[commit_id] = true; + /* + if(!CheckCommit(commit_id)){ + LOG(ERROR)<<"check commit fail:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + { + if(op.version==0){ + //storage_->Store(it.first, op.data); + cache_data_[it.first] = std::make_pair(op.data, 1); + tmp_data.push_back(std::make_pair(it.first, std::make_pair(op.data, 0))); + //LOG(ERROR)<<"save to cache commit id:"<GetVersion(it.first, false); + if(op.version >v){ + //cache_data_[it.first] = std::make_pair(op.data, op.version); + //tmp_data.push_back(std::make_pair(it.first, std::make_pair(op.data, op.version))); + storage_->StoreWithVersion(it.first, op.data, op.version); + //LOG(ERROR)<<"save to cache commit id:"<second.second; + if(op.version >v){ + //cache_data_[it.first] = std::make_pair(op.data, op.version); + //tmp_data.push_back(std::make_pair(it.first, std::make_pair(op.data, op.version))); + storage_->StoreWithVersion(it.first, op.data, op.version); + //LOG(ERROR)<<"save to cache commit id:"<Remove(it.first, false); + done = true; + break; + } + } + } + + } + + for(auto& v : tmp_data){ + if(v.second.second==0){ + storage_->Store(v.first, v.second.first); + } + else { + storage_->StoreWithVersion(v.first, v.second.first, v.second.second, false); + } + } + + done_.push_back(commit_id); + #ifdef CDebug + LOG(ERROR)<<"commit done:"<< commit_id; + #endif + return true; +} + + +bool EController::Commit(int64_t commit_id){ + #ifdef CDebug + LOG(ERROR)<<"commit id:"< list = FetchAbort(); + for(int64_t id : list){ + AddRedo(id); + } + //LOG(ERROR)<<"commit time:"< &EController::GetRedo(){ + return redo_; +} + +const std::vector &EController::GetDone(){ + return done_; +} + +uint64_t EController::GetDelay(int64_t commit_id) { + return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/e_controller.h b/platform/consensus/ordering/fides/executor/x_manager/e_controller.h new file mode 100644 index 000000000..208720bef --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/e_controller.h @@ -0,0 +1,112 @@ +#pragma once + +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class EController : public ConcurrencyController { + public: + EController(DataStorage * storage, int window_size); + virtual ~EController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + ConcurrencyController::ModifyMap * GetChangeList(int64_t commit_id); + + void Clear(); + void Clear(int64_t commit_id); + + uint64_t GetDelay(int64_t commit_id); + + private: + //void Clear(); + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + void RemovePreRecord(const uint256_t& address, + int64_t commit_id); + + enum LockType { + READ = 1, + WRITE = 2 + }; + + bool TryLock(const uint256_t& address, int64_t owner, LockType type); + void ReleaseLock(int64_t commit_id); + void ReleaseLock(int64_t commit_id, const uint256_t& address); + + void Abort(const int64_t commit_id); + +void AddRedo(int64_t commit_id); + std::vector FetchAbort(); + + + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo(){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + std::vector changes_list_; + //std::vector> changes_list_; + typedef std::map> > PreCommitList; + PreCommitList pre_commit_list_ GUARDED_BY(mutex_); + std::map> last_; + //PreCommitList pre_commit_list_[4096] GUARDED_BY(mutex_); + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, db_mutex_; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + std::vector abort_list_; + std::map > cache_data_; + uint64_t commit_time_[2048], commit_delay_[2048]; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/evm_state.h b/platform/consensus/ordering/fides/executor/x_manager/evm_state.h new file mode 100644 index 000000000..ccd0d9500 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/evm_state.h @@ -0,0 +1,23 @@ +#pragma once + +#include "eEVM/globalstate.h" + +namespace resdb { +namespace contract { + +class EVMState : public eevm::GlobalState { +public: + EVMState() = default; + virtual ~EVMState() = default; + +protected: + const eevm::Block& get_current_block() override { return block_; } + uint256_t get_block_hash(uint8_t offset) override { return 0; } + +private: + // Unused. + eevm::Block block_; +}; + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/executor_state.cpp b/platform/consensus/ordering/fides/executor/x_manager/executor_state.cpp new file mode 100644 index 000000000..7e9348801 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/executor_state.cpp @@ -0,0 +1,75 @@ +#include "service/contract/executor/x_manager/executor_state.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + ExecutorState::ExecutorState(ConcurrencyController * controller, int64_t commit_id) : controller_(controller) { + } + + bool ExecutorState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void ExecutorState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState ExecutorState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState ExecutorState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), DBView(controller_, 0, 0)}); + return get(addr); + } + + const eevm::SimpleAccount& ExecutorState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void ExecutorState::Set(const eevm::SimpleAccount& acc, int64_t commit_id, int version) { + Insert({acc, DBView(controller_, commit_id, version)}); + } + + void ExecutorState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } + + bool ExecutorState::Flesh(const Address& addr, int commit_id) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + acc->second.second.Flesh(commit_id); + return true; + } + return false; + } + +/* + bool ExecutorState::Commit(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + return acc->second.second.Commit(); + } + return false; + } + */ + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/executor_state.h b/platform/consensus/ordering/fides/executor/x_manager/executor_state.h new file mode 100644 index 000000000..2e3603617 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/executor_state.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/x_manager/db_view.h" +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { +namespace x_manager { + + class ExecutorState : public EVMState { + public: + using StateEntry = std::pair; + + public: + ExecutorState(ConcurrencyController * controller, int64_t commit_id); + virtual ~ExecutorState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Flesh the local view to the controller with a commit id. + // Once all the contracts have fleshed their changes, they should call commit. + // Return false if contract not exists. + bool Flesh(const eevm::Address& addr, int commit_id); + // Commit the changes using the commit id from the flesh. + //bool Commit(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + void Set(const eevm::SimpleAccount& acc, int64_t commit_id, int version); + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + ConcurrencyController * controller_; + }; + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/fx_committer.cpp new file mode 100644 index 000000000..fcdc15780 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_committer.cpp @@ -0,0 +1,259 @@ +/* + * 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 "service/contract/executor/x_manager/fx_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +FXCommitter:: FXCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + //LOG(ERROR)<<"init window:"<(storage, window_size); + //controller_ = std::make_unique(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + //context_list_.resize(window_size_); + //tmp_resp_list_.resize(window_size_); + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + if(request->GetContractExecuteInfo()->func_params.is_only()){ + controller_->SetOnly(request->GetContractExecuteInfo()->commit_id); + } + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + resp->runtime = track.GetRunTime()*1000; + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + + +FXCommitter::~FXCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void FXCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +ExecutionContext* FXCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void FXCommitter::AsyncExecContract(std::vector& requests) { + return ; +} + +std::vector> FXCommitter::ExecContract( + std::vector& requests) { + + controller_->Clear(); + int id = 1; + std::vector> tmp_resp_list; + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + context_ptr->start_time = GetCurrentTime(); + + AddTask(request.commit_id, std::move(context)); + request_queue_.Push(std::make_unique(context_ptr)); + tmp_resp_list.push_back(nullptr); + } + + tmp_resp_list.push_back(nullptr); + std::vector> resp_list; + + int process_num = id-1; + //LOG(ERROR)<<"wait num:"<0) { + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + + int64_t resp_id= resp->commit_id; + int ret = resp->ret; + #ifdef Debug + LOG(ERROR)<<"resp:"<Commit(resp_id); + if(!commit_ret){ + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + const auto& redo_list = controller_->GetRedo(); + for(int64_t id : redo_list) { + controller_->Clear(id); + auto context_ptr = GetTaskContext(id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + + const auto& done_list = controller_->GetDone(); + for(int id : done_list){ + //tmp_resp_list[id]->rws = *controller_->GetChangeList(id); + tmp_resp_list[id]->delay = controller_->GetDelay(id)/1000.0; + //LOG(ERROR)<<"done :"<rws.size(); + resp_list.push_back(std::move(tmp_resp_list[id])); + process_num--; + continue; + } + //resp_list.push_back(std::move(tmp_resp_list[resp_id])); + //process_num--; + //continue; + } + else { + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + return resp_list; +// LOG(ERROR)<<"last id:"< FXCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_committer.h b/platform/consensus/ordering/fides/executor/x_manager/fx_committer.h new file mode 100644 index 000000000..0afea0b94 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_committer.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/fx_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class FXCommitter : public ContractCommitter { + public: + FXCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~FXCommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + //std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp new file mode 100644 index 000000000..f1d854fbe --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp @@ -0,0 +1,1835 @@ +#include "service/contract/executor/x_manager/fx_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } +} + + +FXController::FXController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< FXController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void FXController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int FXController::GetLastR(const uint256_t& address, int addr_idx){ + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< FXController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set FXController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.upper_bound(idx); + if(data_idx == it->second.begin()){ + } + else { + --data_idx; +#ifdef CDebug + LOG(ERROR)<<"find adress :"<Load(address, false); + data.data = ret.first; + data.version = ret.second; +} + +void FXController::AddDataSnap(int commit_idx, const uint256_t& address, const uint256_t& data, int64_t version) { + std::unique_lock lock(s_mutex_); +#ifdef CDebug + LOG(ERROR)<<"add snap data idx:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + if(!new_cf.empty()){ + std::set r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + } + } + + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect :"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int FXController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool FXController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + //LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + /* + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + AddDataSnap(commit_idx_, it.first, op.data, op.version); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + for(const auto& it : change_set){ + last_commit_ = commit_idx_; + } + commit_idx_++; + +} + + done_.push_back(commit_id); + #ifdef CDebug + LOG(ERROR)<<"commit done:"<< commit_id; + #endif + return true; +} + + +bool FXController::Commit(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"commit id:"< &FXController::GetRedo(){ + return redo_; +} +const std::vector &FXController::GetDone(){ + return done_; +} +uint64_t FXController::GetDelay(int64_t commit_id) { +return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak2 b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak2 new file mode 100644 index 000000000..f2996b7c4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak2 @@ -0,0 +1,1648 @@ +#include "service/contract/executor/x_manager/fx_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } + +} + + +FXController::FXController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< FXController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void FXController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int FXController::GetLastR(const uint256_t& address, int addr_idx){ + + auto git = graph_[addr_idx%2048].find(addr_idx); + if(git == graph_[addr_idx%2048].end()){ + return -1; + } + + GraphInfo *g = &git->second; + + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<lastr_.empty()){ + //LOG(ERROR)<<"no last:"<lastw_.empty()){ + return -1; + } + return *g->lastw_.begin(); + } + + return *g->lastr_.begin(); +} + +int FXController::GetLastW(const uint256_t& address, int addr_idx){ +#ifdef CDebug + LOG(ERROR)<<"get last w, lp size:"<second; + + if(g->lastw_.empty()){ + //LOG(ERROR)<<"no last:"<lastr_.empty()){ + return -1; + } + return *g->lastr_.begin(); + } + return *g->lastw_.begin(); +} + +int FXController::GetLastWOnly(const uint256_t& address, int addr_idx){ + return GetLastW(address, addr_idx); +} + + +void FXController::Abort(int64_t commit_id){ + if(aborted_[commit_id]){ + return; + } + aborted_[commit_id] = true; + //LOG(ERROR)<<"abort :"<lastr_; + auto& lwp = g->lastw_; + if(lrp.find(commit_id) != lrp.end()){ + is_lastr = true; + is_last = true; + } + if(lwp.find(commit_id) != lwp.end()){ + is_lastw = true; + is_last = true; + } + //LOG(ERROR)<<"commit id:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< FXController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set FXController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto git = graph_[addr_idx%2048].find(addr_idx); + assert(git != graph_[addr_idx%2048].end()); + GraphInfo * g = &git->second; + + auto& lrp = g->lastr_; + auto& lwp = g->lastw_; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second; + + int addr_idx_e = addr_idx&window_size_; + if(last_idx==0){ + if(!is_read){ + ds_ = true; + g->lastw_.insert(idx); + } + else { + g->lastr_.insert(idx); + } + addr_pre_[idx][address].insert(last_idx); + root_addr_[addr_idx_e][address].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect add last:"<lastr_; + auto& lwp = g->lastw_; + { + auto it = lrp.find(last_idx); + if(it != lrp.end()){ + lrp.erase(it); + } + } + { + auto it = lwp.find(last_idx); + if(it != lwp.end()){ + lwp.erase(it); + } + } + if(is_read){ + lrp.insert(idx); + } + else { + ds_ = true; + lwp.insert(idx); + } + addr_pre_[idx][address].insert(last_idx); + addr_child_[last_idx][address].insert(idx); + return true; + } + + ds_ = true; + std::vector new_f; + std::vector new_cf; + auto& lrp = g->lastr_; + auto& lwp = g->lastw_; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + if(!new_cf.empty()){ + std::set r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + } + } + + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect :"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int FXController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool FXController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + //LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + + auto git = graph_[addr_idx%2048].find(addr_idx); + assert(git != graph_[addr_idx%2048].end()); + GraphInfo * g = &git->second; + + auto& lrp = g->lastr_; + auto& lwp = g->lastw_; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + /* + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + //LOG(ERROR)<<" commit id done:"< &FXController::GetRedo(){ + return redo_; +} +const std::vector &FXController::GetDone(){ + return done_; +} +uint64_t FXController::GetDelay(int64_t commit_id) { +return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak3 b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak3 new file mode 100644 index 000000000..9f61e8df4 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.cpp.bak3 @@ -0,0 +1,1891 @@ +#include "service/contract/executor/x_manager/fx_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } +} + +FXController::FXController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< FXController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void FXController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int FXController::GetLastR(const uint256_t& address, int addr_idx){ + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<second.empty()){ + //return -1; + //LOG(ERROR)<<"no last:"<second; + //return *--lp.end(); + return *lp.begin(); +} + +int FXController::GetLastW(const uint256_t& address, int addr_idx){ +#ifdef CDebug + LOG(ERROR)<<"get last w, lp size:"<second.empty()){ + /* + if(last_commit_.find(addr_idx) != last_commit_.end()){ + return last_commit_[addr_idx]; + } + */ + //return -1; + //LOG(ERROR)<<"no last:"<second; + //return *--lp.end(); + return *lp.begin(); +} + +int FXController::GetLastWOnly(const uint256_t& address, int addr_idx){ + return GetLastW(address, addr_idx); +} + + +void FXController::Abort(int64_t commit_id){ + if(aborted_[commit_id]){ + return; + } + aborted_[commit_id] = true; + //LOG(ERROR)<<"abort :"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< FXController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set FXController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"< new_c; +std::vector check_c; + if(last_idx==0){ + for(int c : root_addr_[addr_idx_e][address]){ + if(!is_only_[c]){ + assert(!committed_[c]); + new_c.push_back(c); + } + else { + LOG(ERROR)<<"add check child:"<second.find(c) != root_it->second.end()); + root_it->second.erase(root_it->second.find(c)); + assert(addr_pre_[c][address].find(last_idx) != addr_pre_[c][address].end()); + addr_pre_[c][address].erase(addr_pre_[c][address].find(last_idx)); + } + } + else { + LOG(ERROR)<<"addr child:"<second.find(c) != it->second.end()); + it->second.erase(it->second.find(c)); + assert(addr_pre_[c][address].find(last_idx) != addr_pre_[c][address].end()); + addr_pre_[c][address].erase(addr_pre_[c][address].find(last_idx)); + } + } + + for(int c : new_c){ + addr_child_[idx][address].insert(c); + addr_pre_[c][address].insert(idx); + pre_[c].insert(idx); + child_[idx].insert(c); + LOG(ERROR)<<"add child:"<0){ + LOG(ERROR)<<" add check c:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + LOG(ERROR)<<"connect:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + LOG(ERROR)<<"add edge :<"<second; + } + } + else { + last_r = GetLastR(address, addr_id); + } + // LOG(ERROR)<<"get last r:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<second; + //LOG(ERROR)<<"get from last:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + if(is_only_[commit_id]){ + if(!ConnectOnly(commit_id, 0, address, addr_id, data.state!=STORE)){ + return false; + } + } + else { + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + } + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int FXController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool FXController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<second; + //LOG(ERROR)<<"get from last:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + if(is_only_[commit_id]){ + if(!ConnectOnly(commit_id, 0, address, addr_idx, data.state!=STORE)){ + return false; + } + } + else { + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + } + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + //LOG(ERROR)<<" commit id done:"< &FXController::GetRedo(){ + return redo_; +} +const std::vector &FXController::GetDone(){ + return done_; +} +uint64_t FXController::GetDelay(int64_t commit_id) { +return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h new file mode 100644 index 000000000..6f01efdcb --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h @@ -0,0 +1,152 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class FXController : public ConcurrencyController { + public: + FXController(DataStorage * storage, int window_size); + virtual ~FXController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + std::unique_ptr GetChangeList(int64_t commit_id); + + void Clear(); + void Clear(int64_t commit_id); + uint64_t GetDelay(int64_t commit_id); +void SetOnly(int commit_id); + + private: + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + int GetLastR(const uint256_t& address, int addr_id); + int GetLastW(const uint256_t& address, int addr_id); + int GetLastWOnly(const uint256_t& address, int addr_id); + int FindW(int64_t commit_id, const uint256_t& address, int addr_id); + int FindR(int64_t commit_id, const uint256_t& address, int addr_id); + + bool Connect(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + void ConnectSelf(int idx, int last_idx, const uint256_t& address, int addr_idx); + bool AddNewNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + bool AddOldNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + void AddDataToChangeList(std::vector& change_set, const Data& data); + + void RecursiveAbort(int64_t idx, const uint256_t& address); + void AbortNodeFrom(int64_t idx, const uint256_t& address); + void AbortNode(int64_t idx); + bool ContainRead(int commit_id, const uint256_t &address); + + void RemoveNode(int64_t commit_id); + + void Abort(const int64_t commit_id); + bool Reach(int from, int to); + bool PrintReach(int from, int to); + std::set GetReach(int from, int to); + std::set GetReach(int from); + + int GetDep(int from, int to); + void AddRedo(int64_t commit_id); + + int AddressToId(const uint256_t& key); + uint256_t& GetAddress(int key); + + void ResetReach(int64_t commit_id); + void Update(const std::vector& commit_id) ; + void AttachReadOnly(int commit_id, const uint256_t& address); + void ReadReadOnly(int commit_id, const uint256_t& address, Data &data); + void AddDataSnap(int commit_idx, const uint256_t& address, const uint256_t& data, int64_t version); + + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo():type(0){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + //typedef std::map>> KeyMap; + std::vector changes_list_; + std::vector> addr_changes_list_; + //std::vector> changes_list_; + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, k_mutex_[2048], s_mutex_; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + bool has_write_[2048]; + std::vector abort_list_; + std::set pre_[2048]; + std::set child_[2048]; + std::bitset<2048> reach_[2048]; + + std::map> addr_pre_[2048]; + std::map> addr_child_[2048]; + std::map> root_addr_[2048]; + std::unordered_map > lastr_, lastw_; + + std::unordered_map> check_; + std::set check_abort_; + std::vector post_abort_, pending_check_; + std::map key_[2048]; + std::unordered_map akey_; + std::atomic key_id_; + bool ds_ = false; + uint64_t commit_time_[2048], commit_delay_[2048]; + bool is_only_[2048]; + + std::map >> commit_list_; + int last_commit_; + int attach_[2048]; + int commit_idx_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h.bak3 b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h.bak3 new file mode 100644 index 000000000..f55e647ac --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/fx_controller.h.bak3 @@ -0,0 +1,149 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class FXController : public ConcurrencyController { + public: + FXController(DataStorage * storage, int window_size); + virtual ~FXController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + std::unique_ptr GetChangeList(int64_t commit_id); + + void Clear(); + void Clear(int64_t commit_id); + uint64_t GetDelay(int64_t commit_id); +void SetOnly(int commit_id); + + private: + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + int GetLastR(const uint256_t& address, int addr_id); + int GetLastW(const uint256_t& address, int addr_id); + int GetLastWOnly(const uint256_t& address, int addr_id); + int FindW(int64_t commit_id, const uint256_t& address, int addr_id); + int FindR(int64_t commit_id, const uint256_t& address, int addr_id); + + bool Connect(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + void ConnectSelf(int idx, int last_idx, const uint256_t& address, int addr_idx); + bool AddNewNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + bool AddOldNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + void AddDataToChangeList(std::vector& change_set, const Data& data); + + void RecursiveAbort(int64_t idx, const uint256_t& address); + void AbortNodeFrom(int64_t idx, const uint256_t& address); + void AbortNode(int64_t idx); + bool ContainRead(int commit_id, const uint256_t &address); + + void RemoveNode(int64_t commit_id); + + void Abort(const int64_t commit_id); + bool Reach(int from, int to); + bool PrintReach(int from, int to); + std::set GetReach(int from, int to); + std::set GetReach(int from); + + int GetDep(int from, int to); + void AddRedo(int64_t commit_id); + + int AddressToId(const uint256_t& key); + uint256_t& GetAddress(int key); + + void ResetReach(int64_t commit_id); + void Update(const std::vector& commit_id) ; + bool ConnectOnly(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo():type(0){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + //typedef std::map>> KeyMap; + std::vector changes_list_; + std::vector> addr_changes_list_; + //std::vector> changes_list_; + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, k_mutex_[2048]; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + bool has_write_[2048]; + std::vector abort_list_; + std::set pre_[2048]; + std::set child_[2048]; + std::bitset<2048> reach_[2048]; + + std::map> addr_pre_[2048]; + std::map> addr_child_[2048]; + std::map> root_addr_[2048]; + std::unordered_map > lastr_, lastw_; + + std::unordered_map> check_; + std::set check_abort_; + std::vector post_abort_, pending_check_; + std::map key_[2048]; + std::unordered_map akey_; + std::atomic key_id_; + bool ds_ = false; + uint64_t commit_time_[2048], commit_delay_[2048]; + std::unordered_map last_commit_; + std::unordered_map last_commit_only_; + bool is_only_[2048]; + std::map has_addr_write_[2048]; + std::map> last_r_, last_w_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/global_state.cpp b/platform/consensus/ordering/fides/executor/x_manager/global_state.cpp new file mode 100644 index 000000000..3ccd6453e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/global_state.cpp @@ -0,0 +1,52 @@ +#include "service/contract/executor/x_manager/global_state.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + GlobalState::GlobalState(DataStorage * storage) : storage_(storage) { + } + + bool GlobalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void GlobalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState GlobalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState GlobalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), GlobalView(storage_)}); + + return get(addr); + } + + const eevm::SimpleAccount& GlobalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void GlobalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/global_state.h b/platform/consensus/ordering/fides/executor/x_manager/global_state.h new file mode 100644 index 000000000..04767ad28 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/global_state.h @@ -0,0 +1,46 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/x_manager/global_view.h" +#include "service/contract/executor/x_manager/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { +namespace x_manager { + + class GlobalState : public EVMState{ + public: + using StateEntry = std::pair; + + public: + GlobalState(DataStorage* storage); + virtual ~GlobalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + DataStorage* storage_; + }; + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/global_view.cpp b/platform/consensus/ordering/fides/executor/x_manager/global_view.cpp new file mode 100644 index 000000000..dc88a2124 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/global_view.cpp @@ -0,0 +1,27 @@ +#include "service/contract/executor/x_manager/global_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +GlobalView::GlobalView(DataStorage* storage) :storage_(storage){ } + +void GlobalView::store(const uint256_t& key, const uint256_t& value) { + storage_->Store(key, value); +} + +uint256_t GlobalView::load(const uint256_t& key) { + return storage_->Load(key).first; +} + +bool GlobalView::remove(const uint256_t& key) { + return storage_->Remove(key); +} + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/global_view.h b/platform/consensus/ordering/fides/executor/x_manager/global_view.h new file mode 100644 index 000000000..09915d438 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/global_view.h @@ -0,0 +1,28 @@ +#pragma once + +#include + +#include "service/contract/executor/x_manager/data_storage.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class GlobalView : public eevm::Storage { + +public: + GlobalView(DataStorage* storage); + virtual ~GlobalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + +private: + DataStorage * storage_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/leveldb.cpp b/platform/consensus/ordering/fides/executor/x_manager/leveldb.cpp new file mode 100644 index 000000000..b540aed45 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/leveldb.cpp @@ -0,0 +1,30 @@ +#include "service/contract/executor/manager/leveldb.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +LevelDB::LevelDB(){ + db_ = std::make_unique("./"); + db_->SetBatchSize(10000); +} + +void LevelDB::Flush(){ + //LOG(ERROR)<<"flush"; + for(const auto& it : s){ + std::string addr = eevm::to_hex_string(it.first); + std::string value = eevm::to_hex_string(it.second.first); + //LOG(ERROR)<<"addr:"<SetValue(addr, std::string(buf, value.size()+sizeof(it.second.second))); + delete buf; + } +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/leveldb.h b/platform/consensus/ordering/fides/executor/x_manager/leveldb.h new file mode 100644 index 000000000..e76ca274f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/leveldb.h @@ -0,0 +1,26 @@ +#pragma once + +#include +#include + +#include "eEVM/util.h" +#include "service/contract/executor/manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDB : public DataStorage { + +public: + LevelDB(); + virtual ~LevelDB() = default; + + virtual void Flush(); + +private: + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/leveldb_d_storage.cpp b/platform/consensus/ordering/fides/executor/x_manager/leveldb_d_storage.cpp new file mode 100644 index 000000000..9f2f94f1d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/leveldb_d_storage.cpp @@ -0,0 +1,210 @@ +#include "service/contract/executor/x_manager/leveldb_d_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%1024; + } +void Write(const uint256_t& key, const uint256_t& value, int version, ResLevelDB * db){ + std::string addr = eevm::to_hex_string(key); + + const uint8_t* bytes = intx::as_bytes(value); + size_t sz = sizeof(value); + db->SetValue(addr, std::string((const char*)bytes, sz)); + return; +} + +uint256_t Read(const uint256_t& key, ResLevelDB * db){ + std::string addr = eevm::to_hex_string(key); + + std::string v = db->GetValue(addr); + if(v.empty()){ + return 0; + } + + uint8_t tmp[32] = {}; + memcpy(tmp, v.c_str(), 32); + return intx::le::load(tmp); +} + + +void InternalReset(const uint256_t& key, const uint256_t& value, int64_t version, + std::map > * db, std::shared_mutex * mutex, ResLevelDB * storage ) { + + std::unique_lock lock(*mutex); + (*db)[key] = std::make_pair(value,version); + if(storage) { + Write(key, value, version, storage); + } +} + +int64_t InternalStore(const uint256_t& key, const uint256_t& value, + std::map > * db, std::shared_mutex * mutex, ResLevelDB * storage ) { + + std::unique_lock lock(*mutex); + int64_t v = (*db)[key].second; + (*db)[key] = std::make_pair(value,v+1); + if(storage) { + Write(key, value, v+1, storage); + } + return v+1; +} + +std::pair InternalLoad(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex, ResLevelDB * storage){ + + std::shared_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()){ + if(storage) { + return std::make_pair(Read(key, storage), 0); + } + return std::make_pair(0,0); + } + return e->second; +} + +bool InternalRemove(const uint256_t& key, + std::map > * db, + std::shared_mutex * mutex) { + + std::unique_lock lock(*mutex); + auto e = db->find(key); + if (e == db->end()) + return false; + db->erase(e); + return true; +} + +bool InternalExist(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + return db->find(key) != db->end(); +} + +int64_t InternalGetVersion(const uint256_t& key, + const std::map > * db, + std::shared_mutex * mutex) { + std::shared_lock lock(*mutex); + auto it = db->find(key); + if( it == db->end()){ + return 0; + } + return it->second.second; +} + +} + +LevelDB_D_Storage :: LevelDB_D_Storage(){ + db_ = std::make_unique("./"); +} + +int64_t LevelDB_D_Storage::StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local) { +//LOG(ERROR)<<"storage key:"< LevelDB_D_Storage::Load(const uint256_t& key, bool is_local_view) const { + //LOG(ERROR)<<"load key:"<second.second)<<" key:"< +#include + +#include "service/contract/executor/x_manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDB_D_Storage : public DataStorage { +public: + LevelDB_D_Storage(); + +public: + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local); + virtual int64_t StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local); + virtual std::pair Load(const uint256_t& key, bool is_from_local_view) const; + virtual bool Remove(const uint256_t& key, bool is_local); + virtual bool Exist(const uint256_t& key, bool is_local) const; + + virtual int64_t GetVersion(const uint256_t& key, bool is_local) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local); + +protected: + std::map > c_s_[1024]; + mutable std::shared_mutex mutex_[1024]; + + std::map > g_s_[1024]; + mutable std::shared_mutex g_mutex_[1024]; + + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.cpp b/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.cpp new file mode 100644 index 000000000..1e1cb5a14 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.cpp @@ -0,0 +1,122 @@ +#include "service/contract/executor/x_manager/leveldb_storage.h" + +#include "glog/logging.h" + + +namespace resdb { +namespace contract { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return 0; + return v%2048; + } +} + +LevelDBStorage :: LevelDBStorage(){ + db_ = std::make_unique("./"); +} + +void LevelDBStorage::Write(const uint256_t& key, const uint256_t& value, int version){ + std::string addr = eevm::to_hex_string(key); + + const uint8_t* bytes = intx::as_bytes(value); + size_t sz = sizeof(value); + db_->SetValue(addr, std::string((const char*)bytes, sz)); + return; + + //LOG(ERROR)<<"addr:"<SetValue(addr, std::string(buf, sz+sizeof(version))); + LOG(ERROR)<<"write db"; + delete buf; +} + +uint256_t LevelDBStorage::Read(const uint256_t& key) const { + std::string addr = eevm::to_hex_string(key); + + std::string v = db_->GetValue(addr); + if(v.empty()){ + return 0; + } + + uint8_t tmp[32] = {}; + memcpy(tmp, v.c_str(), 32); + return intx::le::load(tmp); +} + +int64_t LevelDBStorage::Store(const uint256_t& key, const uint256_t& value, bool) { + int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + //LOG(ERROR)<<"store key:"< LevelDBStorage::Load(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + //LOG(ERROR)<<"load key:"<second; +} + +bool LevelDBStorage::Remove(const uint256_t& key, bool) { +int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + auto e = s[idx].find(key); + if (e == s[idx].end()) + return false; + s[idx].erase(e); + return true; +} + +bool LevelDBStorage::Exist(const uint256_t& key, bool) const { +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + return s[idx].find(key) != s[idx].end(); +} + +int64_t LevelDBStorage::GetVersion(const uint256_t& key, bool) const{ +int idx = GetHashKey(key); + std::shared_lock lock(mutex_[idx]); + auto it = s[idx].find(key); + if( it == s[idx].end()){ + return 0; + } + return it->second.second; +} + +int64_t LevelDBStorage::StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool ) { + int idx = GetHashKey(key); + std::unique_lock lock(mutex_[idx]); + s[idx][key] = std::make_pair(value,version); + Write(key, value, version); + return 0; +} + + + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.h b/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.h new file mode 100644 index 000000000..c93df7a70 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/leveldb_storage.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +#include "service/contract/executor/x_manager/data_storage.h" +#include "storage/res_leveldb.h" + +namespace resdb { +namespace contract { + +class LevelDBStorage : public DataStorage { + +public: + LevelDBStorage(); + virtual ~LevelDBStorage() = default; + + virtual int64_t Store(const uint256_t& key, const uint256_t& value, bool is_local = false); + virtual std::pair Load(const uint256_t& key, bool is_local_view = false) const; + virtual bool Remove(const uint256_t& key, bool is_local = false); + virtual bool Exist(const uint256_t& key, bool is_local = false) const; + virtual int64_t StoreWithVersion(const uint256_t& key, const uint256_t& value, int version, bool is_local = false); + + virtual int64_t GetVersion(const uint256_t& key, bool is_local = false) const; + + virtual void Reset(const uint256_t& key, const uint256_t& value, int64_t version, bool is_local = false) {} + virtual void Flush(){}; + +private: + void Write(const uint256_t& key, const uint256_t& value, int version); + uint256_t Read(const uint256_t& key) const; + +protected: + std::map > s[4096]; + mutable std::shared_mutex mutex_[4096]; + std::unique_ptr db_; +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/local_state.cpp b/platform/consensus/ordering/fides/executor/x_manager/local_state.cpp new file mode 100644 index 000000000..f1b02cfcb --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/local_state.cpp @@ -0,0 +1,75 @@ +#include "service/contract/executor/x_manager/local_state.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +using eevm::Address; +using eevm::AccountState; +using eevm::Code; +using eevm::SimpleAccount; + + LocalState::LocalState(ConcurrencyController * controller) : controller_(controller) { + } + + bool LocalState::Exists(const eevm::Address& addr) { + return accounts.find(addr) != accounts.cend(); + } + + void LocalState::remove(const Address& addr) { + accounts.erase(addr); + } + + AccountState LocalState::get(const Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()) + return acc->second; + + return create(addr, 0, {}); + } + + AccountState LocalState::create( + const Address& addr, const uint256_t& balance, const Code& code) { + Insert({SimpleAccount(addr, balance, code), LocalView(controller_, 0)}); + return get(addr); + } + + const eevm::SimpleAccount& LocalState::GetAccount(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + return acc->second.first; + } + + void LocalState::Set(const eevm::SimpleAccount& acc, int64_t commit_id) { + Insert({acc, LocalView(controller_, commit_id)}); + } + + void LocalState::Insert(const StateEntry& p) { + const auto ib = accounts.insert(std::make_pair(p.first.get_address(), p)); + + assert(ib.second); + } + + bool LocalState::Flesh(const Address& addr, int commit_id) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + acc->second.second.Flesh(commit_id); + return true; + } + return false; + } + +/* + bool LocalState::Commit(const eevm::Address& addr) { + const auto acc = accounts.find(addr); + if (acc != accounts.cend()){ + return acc->second.second.Commit(); + } + return false; + } + */ + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/local_state.h b/platform/consensus/ordering/fides/executor/x_manager/local_state.h new file mode 100644 index 000000000..82c518aaf --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/local_state.h @@ -0,0 +1,55 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +#pragma once + +#include "service/contract/executor/x_manager/local_view.h" +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/evm_state.h" + +#include "eEVM/simple/simpleaccount.h" + +namespace resdb { +namespace contract { +namespace x_manager { + + class LocalState : public EVMState { + public: + using StateEntry = std::pair; + + public: + LocalState(ConcurrencyController * controller); + virtual ~LocalState() = default; + + virtual void remove(const eevm::Address& addr) override; + + // Get contract by contract address. + eevm::AccountState get(const eevm::Address& addr) override; + + bool Exists(const eevm::Address& addr); + + // Flesh the local view to the controller with a commit id. + // Once all the contracts have fleshed their changes, they should call commit. + // Return false if contract not exists. + bool Flesh(const eevm::Address& addr, int commit_id); + // Commit the changes using the commit id from the flesh. + //bool Commit(const eevm::Address& addr); + + // Create an account for the contract, which the balance is 0. + eevm::AccountState create( + const eevm::Address& addr, const uint256_t& balance, const eevm::Code& code) override; + + const eevm::SimpleAccount& GetAccount(const eevm::Address& addr) ; + void Set(const eevm::SimpleAccount& acc, int64_t commit_id); + + protected: + void Insert(const StateEntry& p); + + private: + std::map accounts; + ConcurrencyController * controller_; + }; + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/local_view.cpp b/platform/consensus/ordering/fides/executor/x_manager/local_view.cpp new file mode 100644 index 000000000..c46eabb00 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/local_view.cpp @@ -0,0 +1,59 @@ +#include "service/contract/executor/x_manager/local_view.h" + +#include "eEVM/util.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +LocalView::LocalView(ConcurrencyController * controller, int64_t commit_id) + :controller_(controller), + commit_id_(commit_id){ } + +void LocalView::store(const uint256_t& key, const uint256_t& value) { + //LOG(ERROR)<<"========= store key:"< load_value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(STORE, value, load_value.second, load_value.first)); + } + else { + const Data& data = local_changes_[key].back(); + if(data.state == LOAD){ + local_changes_[key].push_back(Data(STORE, value, data.version+1)); + } + else { + local_changes_[key].pop_back(); + local_changes_[key].push_back(Data(STORE, value, data.version)); + } + } +} + +uint256_t LocalView::load(const uint256_t& key) { + //LOG(ERROR)<<"load key:"< value = controller_->GetStorage()->Load(key, /*is_from_local=*/true); + local_changes_[key].push_back(Data(LOAD, value.first, value.second)); + return value.first; + } + return it->second.back().data; +} + +bool LocalView::remove(const uint256_t& key) { + local_changes_[key].push_back(Data(REMOVE)); + return true; +} + +void LocalView::Flesh(int64_t commit_id) { + commit_id_ = commit_id; + //LOG(ERROR)<<"commit push:"<PushCommit(commit_id, local_changes_); + local_changes_.clear(); +} + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/local_view.h b/platform/consensus/ordering/fides/executor/x_manager/local_view.h new file mode 100644 index 000000000..3dca1b7fb --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/local_view.h @@ -0,0 +1,40 @@ +#pragma once + +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "eEVM/storage.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class LocalView : public eevm::Storage { + +public: + LocalView(ConcurrencyController * controller, int64_t commit_id); + virtual ~LocalView() = default; + + void store(const uint256_t& key, const uint256_t& value) override; + uint256_t load(const uint256_t& key) override; + bool remove(const uint256_t& key) override; + + // for 2PL, once it is done, all the commit will be pushed to + // the controller to judge if it can be committed. + // During the flesh, all the changes will be removed. + void Flesh(int64_t commit_id); + // Commit the changes. If there is a conflict, return false. + // Make sure all other committers have pushed their changes before calling Commit. + //bool Commit(); + // Remove all the changes. + //void Abort(); + +private: + ConcurrencyController * controller_; + int64_t commit_id_; + std::map> local_changes_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/local_view_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/local_view_test.cpp new file mode 100644 index 000000000..f05672a9f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/local_view_test.cpp @@ -0,0 +1,125 @@ +/* + * 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 "service/contract/executor/manager/local_view.h" +#include "service/contract/executor/manager/two_phase_controller.h" + +#include +#include + +namespace resdb { +namespace contract { +namespace { + +using ::testing::Test; + +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +TEST(LocalViewTest, ViewChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); +} + +TEST(LocalViewTest, CommitChange) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view(&controller, 0); + + EXPECT_EQ(view.load(address1), 2000) ; + view.store(address1, 3000); + EXPECT_EQ(view.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + view.Flesh(0); + EXPECT_TRUE(controller.Commit(0)); + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + +TEST(LocalViewTest, CommitConflict) { + DataStorage storage; + uint256_t address1 = HexToInt("0x123"); + + storage.Store(address1, 2000); + EXPECT_EQ(storage.Load(address1).first, 2000); + + TwoPhaseController controller(&storage); + + LocalView view1(&controller, 0); + LocalView view2(&controller, 1); + + EXPECT_EQ(view1.load(address1), 2000) ; + view1.store(address1, 3000); + EXPECT_EQ(view1.load(address1), 3000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + EXPECT_EQ(view2.load(address1), 2000) ; + view2.store(address1, 4000); + EXPECT_EQ(view2.load(address1), 4000) ; + + // storage still contains 2000 + EXPECT_EQ(storage.Load(address1).first, 2000); + + + view1.Flesh(0); + view2.Flesh(1); + + EXPECT_FALSE(controller.Commit(1)); + EXPECT_TRUE(controller.Commit(0)); + + // Save to real storage. + EXPECT_EQ(storage.Load(address1).first, 3000); +} + + + +} // namespace +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/mock_d_storage.h b/platform/consensus/ordering/fides/executor/x_manager/mock_d_storage.h new file mode 100644 index 000000000..976bf9e7f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/mock_d_storage.h @@ -0,0 +1,50 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/x_manager/d_storage.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class MockDStorage : public D_Storage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value, bool), (override)); + MOCK_METHOD(int64_t, StoreWithVersion, (const uint256_t& key, const uint256_t& value, int version, bool), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&, bool), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(void, Reset, (const uint256_t&, const uint256_t&, int64_t, bool), (override)); +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/mock_data_storage.h b/platform/consensus/ordering/fides/executor/x_manager/mock_data_storage.h new file mode 100644 index 000000000..fa6743374 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/mock_data_storage.h @@ -0,0 +1,46 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/x_manager/data_storage.h" + +namespace resdb { +namespace contract { + +class MockStorage : public DataStorage { + public: + typedef std::pair LoadType; + + MOCK_METHOD(int64_t, Store, (const uint256_t& key, const uint256_t& value, bool), (override)); + MOCK_METHOD(bool, Remove, (const uint256_t&, bool), (override)); + MOCK_METHOD(bool, Exist, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(int64_t, GetVersion, (const uint256_t&, bool), (const, override)); + MOCK_METHOD(LoadType, Load, (const uint256_t&, bool), (const, override)); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/mock_e_controller.h b/platform/consensus/ordering/fides/executor/x_manager/mock_e_controller.h new file mode 100644 index 000000000..afdfc58bd --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/mock_e_controller.h @@ -0,0 +1,45 @@ +/* + * 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 "gmock/gmock.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class MockEController : public StreamingEController { + public: + MockEController(DataStorage* storage, int window):StreamingEController(storage, window){} + + MOCK_METHOD(void, Store, (const int64_t, const uint256_t& key, const uint256_t& value, int), (override)); + MOCK_METHOD(uint256_t, Load, (const int64_t, const uint256_t&, int), (override)); +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/seq_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/seq_committer.cpp new file mode 100644 index 000000000..a3f5f8193 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/seq_committer.cpp @@ -0,0 +1,93 @@ +/* + * 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 "service/contract/executor/x_manager/seq_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +SeqCommitter:: SeqCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + executor_ = std::make_unique(); +} + + +SeqCommitter::~SeqCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; +} + +void SeqCommitter::AsyncExecContract(std::vector& requests) { + return ; +} + +std::vector> SeqCommitter::ExecContract( + std::vector& requests) { + std::vector> resp_list; + for(auto& request: requests) { + auto ret = ExecContract(request.caller_address, + request.contract_address, + request.func_addr, + request.func_params, gs_); + std::unique_ptr resp = std::make_unique(); + resp->contract_address = request.contract_address; + resp->commit_id = request.commit_id; + resp->user_id = request.user_id; + resp->ret = 0; + resp->result = *ret; + resp_list.push_back(std::move(resp)); + } + return resp_list; +} + +absl::StatusOr SeqCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/seq_committer.h b/platform/consensus/ordering/fides/executor/x_manager/seq_committer.h new file mode 100644 index 000000000..5db60b3cf --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/seq_committer.h @@ -0,0 +1,92 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class SeqCommitter : public ContractCommitter { + public: + SeqCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~SeqCommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::unique_ptr executor_; + + std::vector> resp_list_; + + const int worker_num_; + int window_size_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/seq_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/seq_controller.cpp new file mode 100644 index 000000000..38d6b06e9 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/seq_controller.cpp @@ -0,0 +1,1758 @@ +#include "service/contract/executor/x_manager/x_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } + + +XController::XController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< XController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void XController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int XController::GetLastR(const uint256_t& address, int addr_idx){ + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< XController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set XController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + if(!new_cf.empty()){ + std::set r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + } + } + + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect :"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int XController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool XController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + //LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + /* + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + //LOG(ERROR)<<" commit id done:"< &XController::GetRedo(){ + return redo_; +} +const std::vector &XController::GetDone(){ + return done_; +} + +uint64_t XController::GetDelay(int64_t commit_id) { + return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/seq_controller.h b/platform/consensus/ordering/fides/executor/x_manager/seq_controller.h new file mode 100644 index 000000000..495e92f66 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/seq_controller.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class XController : public ConcurrencyController { + public: + XController(DataStorage * storage, int window_size); + virtual ~XController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + std::unique_ptr GetChangeList(int64_t commit_id); + + void Clear(); +void Clear(int64_t commit_id); + uint64_t GetDelay(int64_t commit_id); + + private: + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + int GetLastR(const uint256_t& address, int addr_id); + int GetLastW(const uint256_t& address, int addr_id); + int GetLastWOnly(const uint256_t& address, int addr_id); + int FindW(int64_t commit_id, const uint256_t& address, int addr_id); + int FindR(int64_t commit_id, const uint256_t& address, int addr_id); + + bool Connect(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + void ConnectSelf(int idx, int last_idx, const uint256_t& address, int addr_idx); + bool AddNewNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + bool AddOldNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + void AddDataToChangeList(std::vector& change_set, const Data& data); + + void RecursiveAbort(int64_t idx, const uint256_t& address); + void AbortNodeFrom(int64_t idx, const uint256_t& address); + void AbortNode(int64_t idx); + bool ContainRead(int commit_id, const uint256_t &address); + + void RemoveNode(int64_t commit_id); + + void Abort(const int64_t commit_id); + bool Reach(int from, int to); + bool PrintReach(int from, int to); + std::set GetReach(int from, int to); + std::set GetReach(int from); + + int GetDep(int from, int to); + void AddRedo(int64_t commit_id); + + int AddressToId(const uint256_t& key); + uint256_t& GetAddress(int key); + + void ResetReach(int64_t commit_id); +void Update(const std::vector& commit_id) ; + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo():type(0){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + //typedef std::map>> KeyMap; + std::vector changes_list_; + std::vector> addr_changes_list_; + //std::vector> changes_list_; + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, k_mutex_[2048]; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + bool has_write_[2048]; + std::vector abort_list_; + std::set pre_[2048]; + std::set child_[2048]; + std::bitset<2048> reach_[2048]; + + std::map> addr_pre_[2048]; + std::map> addr_child_[2048]; + std::map> root_addr_[2048]; + std::unordered_map > lastr_, lastw_; + + std::unordered_map> check_; + std::set check_abort_; + std::vector post_abort_, pending_check_; + std::map key_[2048]; + std::unordered_map akey_; + std::atomic key_id_; + bool ds_ = false; + uint64_t commit_time_[2048], commit_delay_[2048]; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.cpp new file mode 100644 index 000000000..f4a5d8d6f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.cpp @@ -0,0 +1,324 @@ +/* + * 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 "service/contract/executor/x_manager/streaming_e_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define DEBUG + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; +} + +StreamingECommitter:: StreamingECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back, + int worker_num):storage_(storage), gs_(global_state), + worker_num_(worker_num), + window_size_(window_size), + call_back_(call_back) { + + //LOG(ERROR)<<"init window:"<(storage, window_size*2); + executor_ = std::make_unique(); + +// LOG(ERROR)<<"init"; + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + num_ = 0; + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id<<" param:"<< request->GetContractExecuteInfo()->func_params.DebugString(); + //LOG(ERROR)<<"========= get resp commit id:"<GetContractExecuteInfo()->commit_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + //assert(resp->retry_time<=5); + resp->runtime = track.GetRunTime()*1000; + //LOG(ERROR)<<"run:"<runtime; + //local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + // request->GetContractExecuteInfo()->commit_id); + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + //assert(resp->ret>=0); + } + resp_queue_.Push(std::move(resp)); + } + })); + } + + response_ = std::thread([&]() { + while (!is_stop_) { + ResponseProcess(); + } + }); +} + +void StreamingECommitter::SetExecuteCallBack(std::function)> func) { + call_back_ = std::move(func); +} + +void StreamingECommitter::Clear(){ + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + num_ = 0; + first_id_ = 0; + last_id_ = 1; + id_ = 1; +} + +StreamingECommitter::~StreamingECommitter(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } + if(response_.joinable()){ + response_.join(); + } +} + +void StreamingECommitter::SetController(std::unique_ptr controller) { + controller_ = std::move(controller); +} + +void StreamingECommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id%window_size_] = std::move(context); +} + +void StreamingECommitter::RemoveTask(int64_t commit_id){ + context_list_.erase(context_list_.find(commit_id)); +} + +ExecutionContext* StreamingECommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id%window_size_].get(); +} + +void StreamingECommitter::CallBack(uint64_t commit_id){ + int idx = commit_id%window_size_; + //LOG(ERROR)<<"call back:"< lk(mutex_); + cv_.notify_all(); + num_--; + } +} + +bool StreamingECommitter::WaitNext(){ + int c = 64; + while(!is_stop_) { + int timeout_ms = 10000; + std::unique_lock lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return num_ lk(mutex_); + cv_.wait_for(lk, std::chrono::microseconds(timeout_ms), [&] { + return first_id_>0&&first_id_%500==0; + //return id_ - first_id_commit_id; + int idx = resp->commit_id % window_size_; + +#ifdef DEBUG + LOG(ERROR)<<"recv :"<ret<<" last id:"<Commit(resp_commit_id); + std::vector next_commit = controller_->GetRedo(); + for(int64_t new_next : next_commit) { + auto context_ptr = GetTaskContext(new_next); + context_ptr->SetRedo(); +#ifdef DEBUG + LOG(ERROR)<<"redo :"<RedoTime(); +#endif + controller_->Clear(new_next); + request_queue_.Push(std::make_unique(context_ptr)); + } + + std::vector done_list = controller_->GetDone(); + for(int64_t done_id : done_list) { + //LOG(ERROR)<<"get doen id:"<& requests) { + Clear(); + controller_->Clear(); + + for(auto& request: requests) { + if(!WaitNext()){ + return; + } + num_++; + int cur_idx = id_%window_size_; + assert(id_>=last_id_); + + request.commit_id = id_++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + + //LOG(ERROR)<<"execute:"<Clear(request.commit_id); + request_queue_.Push(std::make_unique(context_ptr)); + } + + return ; +} + +absl::StatusOr StreamingECommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.h b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.h new file mode 100644 index 000000000..ba6fd3158 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer.h @@ -0,0 +1,109 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class StreamingECommitter : public ContractCommitter { + public: + StreamingECommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + std::function)> call_back = nullptr, + int worker_num = 2); + + ~StreamingECommitter(); + + void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info) { return {}; } + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + void Clear(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + std::unique_ptr executor_; + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; + std::atomic num_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer_test.cpp b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer_test.cpp new file mode 100644 index 000000000..3bb7f26df --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/streaming_e_committer_test.cpp @@ -0,0 +1,843 @@ +/* + * 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 "service/contract/executor/x_manager/streaming_e_committer.h" + +#include + +#include "service/contract/executor/x_manager/mock_d_storage.h" +#include "service/contract/executor/x_manager/mock_e_controller.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_deployer.h" +#include "service/contract/executor/x_manager/address_manager.h" +#include "service/contract/proto/func_params.pb.h" + +#include +#include + + +namespace resdb { +namespace contract { +namespace x_manager { +namespace { + +using ::testing::Test; +using ::testing::Invoke; + +const std::string test_dir = "/home/ubuntu/nexres//service/contract/executor/manager/"; +//const std::string test_dir = std::string(getenv("TEST_SRCDIR")) + "/" + + // std::string(getenv("TEST_WORKSPACE")) + + // "/service/contract/executor/manager/"; + +Address get_random_address() { return AddressManager().CreateRandomAddress(); } + +std::string U256ToString(uint256_t v) { return eevm::to_hex_string(v); } +uint256_t HexToInt(const std::string& v) { return eevm::to_uint256(v); } + +class StreamingECommitterTest : public Test { + public: + StreamingECommitterTest() : owner_address_(get_random_address()) { + std::string contract_path = test_dir + "test_data/kv.json"; + + std::ifstream contract_fstream(contract_path); + if (!contract_fstream) { + throw std::runtime_error(fmt::format( + "Unable to open contract definition file {}", contract_path)); + } + + const auto contracts_definition = nlohmann::json::parse(contract_fstream); + const auto all_contracts = contracts_definition["contracts"]; + const auto contract_code = all_contracts["kv.sol:KV"]; + storage_ = std::make_unique(); + contract_json_ = contract_code; + + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + auto ret = data_[address]; + //LOG(ERROR)<<"load:"<(storage_.get()); + + auto controller = std::make_unique(storage_.get(), 10); + controller_ = controller.get(); + + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + return controller_->LoadInternal(commit_id, address, version); + })); + + EXPECT_CALL(*controller_, Store).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version) { + return controller_->StoreInternal(commit_id, key, value, version); + })); + + + committer_ = std::make_unique(storage_.get(), gs_.get(), 10); + committer_->SetController(std::move(controller)); + + contract_address_ = AddressManager::CreateContractAddress(owner_address_); + deployer_ = std::make_unique(committer_.get(), gs_.get()); + contract_address_ = deployer_->DeployContract(owner_address_, contract_json_, {1000}); + } + + void ExecContract(std::vector& execute_info) { + for(int i = 0; i < execute_info.size();++i){ + std::string func_addr = + deployer_->GetFuncAddress(execute_info[i].contract_address, execute_info[i].func_params.func_name()); + if (func_addr.empty()) { + LOG(ERROR) << "no fouction:" << execute_info[i].func_params.func_name(); + execute_info[i].contract_address = 0; + continue; + } + execute_info[i].func_addr = func_addr; + execute_info[i].commit_id = i+1; + } + committer_->AsyncExecContract(execute_info); + } + + protected: + Address owner_address_; + Address contract_address_; + nlohmann::json contract_json_; + std::unique_ptr storage_; + std::unique_ptr gs_; + std::unique_ptrcommitter_; + MockEController* controller_; + std::map> data_;//, g_data_; + std::unique_ptr deployer_; +}; + +TEST_F(StreamingECommitterTest, ExecContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(owner_address_)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done.set_value(true); + }); + + { + ExecContract(info); + } + done_future.get(); + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + //LOG(ERROR)<<"owner key:"< done; + std::future done_future = done.get_future(); + std::vector info; + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + assert(resp != nullptr); + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); + LOG(ERROR)<<"===== done"; +} + +TEST_F(StreamingECommitterTest, ExecConflictContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::set ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + ids.insert(commit_id); + return controller_->LoadInternal(commit_id, address, version); + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==2){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); + +} + +TEST_F(StreamingECommitterTest, ExecConflictContractAhead2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(100)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("get(address)"); + func_params.add_param(U256ToString(transfer_receiver3)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + EXPECT_EQ(ids[2], 1); + EXPECT_EQ(ids[1], 0); + EXPECT_EQ(ids[3], 0); + + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + EXPECT_EQ(data_[owner_key].first, 800); + EXPECT_EQ(data_[recer].first, 100); + EXPECT_EQ(data_[recer2].first, 100); + +} + +TEST_F(StreamingECommitterTest, ExecConflictContractAhead) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 300); + EXPECT_EQ(data_[recer3].first, 500); +} + +TEST_F(StreamingECommitterTest, ExecConflictPreCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::map ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==1){ + while(ids.find(2) == ids.end()){ + sleep(1); + } + } + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 800); +} + + +TEST_F(StreamingECommitterTest, ExecConflictPreCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + load_time[address]++; + } + auto ret = data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + return 1; + })); + + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 900); + EXPECT_EQ(data_[recer].first, 300); + EXPECT_EQ(data_[recer2].first, 300); +} + +TEST_F(StreamingECommitterTest, ExecConflictCommitContract) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + + bool start = false; + std::map load_time; + EXPECT_CALL(*storage_, Load).WillRepeatedly(Invoke([&](const uint256_t& address, bool is_local) { + if(is_local){ + load_time[address]++; + } + auto ret = data_[address]; + LOG(ERROR)<<"load add:"<1){ + done = true; + break; + } + } + } + } + + int v = data_[key].second; + data_[key] = std::make_pair(value, v+1); + return 1; + })); + + EXPECT_CALL(*storage_, Reset).WillRepeatedly(Invoke([&](const uint256_t& key, const uint256_t& value, int64_t version, bool is_local) { + LOG(ERROR)<<"reset key:"< done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==3){ + done.set_value(true); + } + }); + + start = true; + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + EXPECT_EQ(data_[owner_key].first, 400); + EXPECT_EQ(data_[recer].first, 500); + EXPECT_EQ(data_[recer2].first, 300); + +} + +// rollback two columns +TEST_F(StreamingECommitterTest, ExecConflictCommitContract2) { + // owner 1000 + std::promise done; + std::future done_future = done.get_future(); + std::vector info; + + std::set pre_ids; + controller_->SetPrecommitCallback([&](int64_t id){ + LOG(ERROR)<<"precommit done:"< ids; + EXPECT_CALL(*controller_, Load).WillRepeatedly(Invoke([&]( + const int64_t commit_id, const uint256_t& address, int version) { + if(commit_id==2 && version>0){ + while(pre_ids.size()<3){ + LOG(ERROR)<<"======== load :"<LoadInternal(commit_id, address, version); + })); + + Address transfer_receiver = get_random_address(); + Address transfer_receiver2 = get_random_address(); + Address transfer_receiver3 = get_random_address(); + Address transfer_receiver4 = get_random_address(); + + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transferif(address,address,address,uint256)"); + func_params.add_param(U256ToString(owner_address_)); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver2)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("transfer(address,address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(300)); + + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver3)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + { + Params func_params; + func_params.set_func_name("set(address,uint256)"); + func_params.add_param(U256ToString(transfer_receiver)); + func_params.add_param(U256ToString(500)); + info.push_back(ContractExecuteInfo(owner_address_, contract_address_, "", func_params, 0)); + } + + std::set done_list; + committer_->SetExecuteCallBack([&](std::unique_ptr resp){ + LOG(ERROR)<<"!!!!! get resp:"<commit_id; + done_list.insert(resp->commit_id); + if(done_list.size()==6){ + done.set_value(true); + } + }); + + { + ExecContract(info); + } + done_future.get(); + + uint256_t owner_key = AddressManager::AddressToSHAKey(owner_address_); + uint256_t recer = AddressManager::AddressToSHAKey(transfer_receiver); + uint256_t recer2 = AddressManager::AddressToSHAKey(transfer_receiver2); + uint256_t recer3 = AddressManager::AddressToSHAKey(transfer_receiver3); + LOG(ERROR)<<"recr:"< +#include +#include + +#include "eEVM/exception.h" +#include "common/utils/utils.h" + +//#define CDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + + int GetHashKey(const uint256_t& address){ + // Get big-endian form + uint8_t arr[32] = {}; + memset(arr,0,sizeof(arr)); + intx::be::store(arr, address); + uint32_t v = 0; + for(int i = 0; i < 32; ++i){ + v += arr[i]; + } + + /* + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + */ + return v%1; + } +} + +StreamingEController::StreamingEController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(static_cast(storage)){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< precommit_callback) { + precommit_callback_ = std::move(precommit_callback); +} + +void StreamingEController::Clear(){ + last_commit_id_=1; + current_commit_id_=1; + last_pre_commit_id_=0; + + assert(window_size_+1<=4096); + //is_redo_.resize(window_size_+1); + //is_done_.resize(window_size_+1); + changes_list_.resize(window_size_+1); + rechanges_list_.resize(window_size_+1); + + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + rechanges_list_[i].clear(); + is_redo_[i] = 0; + is_invalid_[i] = 0; + commit_[i] = 0; + version_[i] = 0; + is_start_[i] = true; + //is_done_[i] = false; + } + + for(int i = 0; i < 4096; ++i){ + commit_list_[i].clear(); + } + pre_commit_list_.clear(); +} + +void StreamingEController::GenRedo(){ + //std::unique_lock lock(mutex_[commit_id&window_size_]); + + return; +} + +std::vector& StreamingEController::GetRedo(){ + //std::unique_lock lock(mutex_[commit_id&window_size_]); + return redo_; +} + +std::vector& StreamingEController::GetDone(){ + return done_; +} + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + + +// ======================================================== +void StreamingEController::StoreInternal(const int64_t commit_id, const uint256_t& address, const uint256_t& value, int version) { + { + std::unique_lock lock(valid_mutex_[commit_id&window_size_]); + if(is_invalid_[commit_id&window_size_]){ + //LOG(ERROR)<<"aborted:"< lk(abort_mutex_); + abort_list_.push_back(commit_id); +} + +std::vector StreamingEController::FetchAbort(){ + std::vector p; + { + std::lock_guard lk(abort_mutex_); + p = abort_list_; + abort_list_.clear(); + } + return p; +} + + +void StreamingEController::AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data, int version) { + + data.commit_version = version; +#ifdef CDebug + LOG(ERROR)<<"append record addr:"< lk(mutex_[hash_idx]); + std::unique_lock lock(g_mutex_); + //std::unique_lock lock(mutex_[hash_idx]); + if(is_invalid_[commit_id]){ + #ifdef CDebug + LOG(ERROR)<<"append commit id:"<> tmp; + while(!commit_set.empty()){ + if(commit_set.back()->commit_id > commit_id){ + //LOG(ERROR)<<"push :"<commit_id; + tmp.push(std::move(commit_set.back())); + commit_set.pop_back(); + } + else { + break; + } + } + + if(data.state == LOAD){ + if(commit_set.empty()){ + auto ret = storage_->Load(address, false); + data.data = ret.first; + data.version = ret.second; + //LOG(ERROR)<<"LOAD from db:"<<" version:"<data.data; + data.version = commit_set.back()->data.version; + //LOG(ERROR)<<"LOAD from :"<second->commit_id<<" version:"<< it->second->data.version; + } + } + else { + if(commit_set.empty()){ + auto ret = storage_->Load(address, false); + data.version = ret.second+1; + //LOG(ERROR)<<"LOAD from db:"; + } + else { + data.version = commit_set.back()->data.version+1; + //LOG(ERROR)<<"LOAD from :"<second->commit_id<<" version:"<second->data.version; + } + } + + int type = 1; + if(data.state == LOAD){ + type = 1; + } + else { + type = 2; + } + + if(!commit_set.empty()){ + if( commit_set.back()->commit_id == commit_id){ + commit_set.back()->type |= type; + commit_set.back()->data = data; + } + else { + commit_set.push_back(std::make_unique(commit_id, type, data)); + } + //LOG(ERROR)<<"back type:"<type; + } + else { + commit_set.push_back(std::make_unique(commit_id, type, data)); + } + + while(!tmp.empty()){ + int ok = 1; + if(data.state == STORE){ + if(tmp.top()->type & 1){ +#ifdef CDebug + LOG(ERROR)<<"abbort:"<commit_id<<" from:"<commit_id); + } + } + if(ok){ + commit_set.push_back(std::move(tmp.top())); + } + tmp.pop(); + } + } + + int idx = commit_id&window_size_; + //std::unique_lock lock(change_list_mutex_[idx]); + auto& change_set = changes_list_[idx][address]; + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD && !change_set.empty()){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); + rechanges_list_[idx].insert(address); + //LOG(ERROR)<<"append record addr:"<> tmp; + while(!commit_set.empty()){ + auto& it = commit_set.back(); + if(it->commit_id != commit_id){ + //LOG(ERROR)<<"push queue:"<commit_id<<" address:"<commit_id == commit_id){ + type = it->type; + //LOG(ERROR)<<"remove old data:"<type&1){ + tmp.top()->version=-1; + #ifdef CDebug + LOG(ERROR)<<"abort :"<commit_id<<" from release:"<commit_id); + } + else if((tmp.top()->type&2)){ + flag = false; + } + } + if(ok){ + commit_set.push_back(std::move(tmp.top())); + } + tmp.pop(); + } + //LOG(ERROR)<<"add size:"<commit_id != commit_id){ + LOG(ERROR)<<"not the head:"<commit_id<<" address:"<commit_id == commit_id); + commit_set.pop_front(); + /* + if(!commit_set.empty()){ + LOG(ERROR)<<"after commit:"<commit_id<<" commit id:"<commit_id != commit_id); + } + else { + LOG(ERROR)<<"after commit empty. commit id:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + commit_[commit_id&window_size_]=true; + } + + CommitDone(commit_id); + last_commit_id_++; + + return true; +} + + +// ================= post commit ======================= + +void StreamingEController::CommitDone(int64_t commit_id) { +#ifdef CDebug + LOG(ERROR)<<"commit done:"<GetVersion(it.first, false); + if(op.state == STORE){ + if(op.version != v+1){ + LOG(ERROR)<<"state:"< +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/d_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class StreamingEController : public ConcurrencyController { + public: + StreamingEController(DataStorage * storage, int window_size); + virtual ~StreamingEController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo(){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + typedef std::map> > PreCommitList; + //typedef std::map> > PreCommitList; + + void GenRedo(); + std::vector& GetRedo(); + std::vector& GetDone(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + void SetPrecommitCallback( std::function precommit_callback); + + void Release(int64_t commit_id); + void Clear(int64_t commit_id); + bool ExecuteDone(int64_t commit_id); + bool CheckCommit(int64_t commit_id); + + bool CheckRedo(int64_t commit_id); + void Clear(); + + private: + + void Abort(int64_t commit_id); + int64_t Release(int64_t commit_id, const uint256_t& address, bool need_abort); + + //void ReleaseCommit(int64_t commit_id); + void ReleaseCommit(int64_t commit_id, const uint256_t& address); + + + bool PreCommit(int64_t commit_id); + bool PostCommit(int64_t commit_id); + + void RedoConflict(int64_t commit_id); + bool CheckPreCommit(int64_t commit_id); + bool CheckFirstFromPreCommit(const uint256_t& address, int64_t commit_id); + bool CheckFirstFromCommit(const uint256_t& address, int64_t commit_id); + + void Remove(int64_t commit_id); + void CleanOldData(int64_t commit_id); + + void Rollback(int64_t id, const uint256_t& address); + void Rollback(); + + int64_t RemovePrecommitRecord(const uint256_t& address, int64_t commit_id); + void RemovePreRecord(const uint256_t& address, + int64_t commit_id); + + bool CheckCommit(int64_t commit_id, bool is_pre); + void CommitDone(int64_t commit_id); + void RedoCommit(int64_t commit_id, int flag); + std::set RollCommit(int64_t commit_id); + std::set AbortCommit(int64_t commit_id, const uint256_t& address); + + void RollBackData(const uint256_t& address, const Data& data); + + void AppendRecord(const uint256_t& address, int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data, int version); + void AppendPreRecord(const uint256_t& address, int hash_idx, + const std::vector& commit_id); + + void ClearOldData(const int64_t commit_id); + void AddRedo(int64_t commit_id); +std::vector FetchAbort(); + + private: + int window_size_ = 1000; + + std::vector changes_list_; + std::vector>rechanges_list_; + + CommitList commit_list_[4096]; + int64_t last_commit_id_, current_commit_id_; + bool commit_[4096]; + + PreCommitList pre_commit_list_ GUARDED_BY(mutex_); + int64_t last_pre_commit_id_; + + std::atomic is_redo_[4096]; + + std::vector redo_; + + //std::atomic is_done_[1024]; + std::vector done_; + + D_Storage * storage_; + std::mutex mutex_[4096], change_list_mutex_[4096], valid_mutex_[4096], rb_mutex_, g_mutex_, abort_mutex_; + //mutable std::shared_mutex mutex_[4096], change_list_mutex_[4096], valid_mutex_[4096], rb_mutex_; + + bool is_invalid_[4096], is_start_[4096], wait_[4096]; + int version_[4096]; + std::function precommit_callback_; + std::set forward_[4096], back_[4096]; + + typedef std::set RedoList; + RedoList redo_list_ GUARDED_BY(mutex_); + void CheckRedo(); + + std::vector abort_list_; + std::set pd_; + std::setredo_p_; + std::map> num_; + std::map ref_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/test_committer.cpp new file mode 100644 index 000000000..e69912d0b --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_committer.cpp @@ -0,0 +1,80 @@ +/* + * 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 "service/contract/executor/manager/test_committer.h" + +#include "glog/logging.h" +//#include "eEVM/processor.h" + +namespace resdb { +namespace contract { + +TestCommitter:: TestCommitter( + DataStorage* storage, + GlobalState * global_state):gs_(global_state) { + + controller_ = std::make_unique(storage); + executor_ = std::make_unique(); +} + +TestCommitter::~TestCommitter(){ +} + +std::vector> TestCommitter::ExecContract( + const std::vector& requests) { + + std::vector > resp_list; + for(const auto& request: requests){ + std::unique_ptr resp = std::make_unique(); + //auto start_time = GetCurrentTime(); + auto ret = ExecContract(request.caller_address, request.contract_address, + request.func_addr, + request.func_params, gs_); + resp->state = ret.status(); + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + } + else { + LOG(ERROR)<<"exec fail"; + resp->ret = -1; + } + resp->contract_address = request.contract_address; + resp->commit_id = request.commit_id; + + resp_list.push_back(std::move(resp)); + } + return resp_list; +} + +absl::StatusOr TestCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + return executor_->ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_committer.h b/platform/consensus/ordering/fides/executor/x_manager/test_committer.h new file mode 100644 index 000000000..0d98bafba --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_committer.h @@ -0,0 +1,63 @@ +/* + * 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 "service/contract/executor/manager/global_state.h" +#include "service/contract/executor/manager/contract_executor.h" +#include "service/contract/executor/manager/contract_committer.h" +#include "service/contract/executor/manager/test_controller.h" +#include "service/contract/proto/func_params.pb.h" + +#include "absl/status/statusor.h" + +namespace resdb { +namespace contract { + +class TestCommitter : public ContractCommitter { + public: + TestCommitter( + DataStorage* storage, + GlobalState * global_state); + + virtual ~TestCommitter(); + + std::vector> ExecContract(const std::vector& request); + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + private: + std::unique_ptr controller_; + GlobalState* gs_; + std::unique_ptr executor_; +}; + +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/test_controller.cpp new file mode 100644 index 000000000..97edd57ed --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_controller.cpp @@ -0,0 +1,35 @@ +#include "service/contract/executor/manager/test_controller.h" + +#include + +namespace resdb { +namespace contract { + +TestController::TestController(DataStorage * storage) : ConcurrencyController(storage){} + +void TestController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + for(auto it : local_changes){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return ; +} + +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_controller.h b/platform/consensus/ordering/fides/executor/x_manager/test_controller.h new file mode 100644 index 000000000..a4371033a --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_controller.h @@ -0,0 +1,19 @@ +#pragma once + +#include "service/contract/executor/manager/concurrency_controller.h" + +#include +#include + +namespace resdb { +namespace contract { + +class TestController : public ConcurrencyController { + public: + TestController(DataStorage * storage); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); +}; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_data/BUILD b/platform/consensus/ordering/fides/executor/x_manager/test_data/BUILD new file mode 100644 index 000000000..6f9052157 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_data/BUILD @@ -0,0 +1 @@ +exports_files(["contract.json", "kv.json"]) diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_data/compile.sh b/platform/consensus/ordering/fides/executor/x_manager/test_data/compile.sh new file mode 100644 index 000000000..dfd25e66f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_data/compile.sh @@ -0,0 +1,5 @@ +# sudo add-apt-repository ppa:ethereum/ethereum +# sudo apt-get update +# sudo apt-get install solc +solc --evm-version homestead --combined-json bin,hashes --pretty-json --optimize kv.sol > kv.json + diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_data/contract.json b/platform/consensus/ordering/fides/executor/x_manager/test_data/contract.json new file mode 100644 index 000000000..e9c0d972f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_data/contract.json @@ -0,0 +1,19 @@ +{ + "contracts": + { + "ERC20.sol:ERC20Token": + { + "bin": "608060405234801561001057600080fd5b506040516104423803806104428339818101604052602081101561003357600080fd5b50516000818155338152600160205260409020556103ec806100566000396000f3fe608060405234801561001057600080fd5b506004361061007e577c01000000000000000000000000000000000000000000000000000000006000350463095ea7b3811461008357806318160ddd146100c357806323b872dd146100dd57806370a0823114610113578063a9059cbb14610139578063dd62ed3e14610165575b600080fd5b6100af6004803603604081101561009957600080fd5b50600160a060020a038135169060200135610193565b604080519115158252519081900360200190f35b6100cb6101fa565b60408051918252519081900360200190f35b6100af600480360360608110156100f357600080fd5b50600160a060020a03813581169160208101359091169060400135610200565b6100cb6004803603602081101561012957600080fd5b5035600160a060020a03166102e6565b6100af6004803603604081101561014f57600080fd5b50600160a060020a038135169060200135610301565b6100cb6004803603604081101561017b57600080fd5b50600160a060020a038135811691602001351661038c565b336000818152600260209081526040808320600160a060020a038716808552908352818420869055815186815291519394909390927f8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925928290030190a35060015b92915050565b60005490565b600160a060020a038316600090815260016020526040812054821180159061024b5750600160a060020a03841660009081526002602090815260408083203384529091529020548211155b156102db57600160a060020a038085166000818152600160209081526040808320805488900390559387168083528483208054880190559282526002815283822033808452908252918490208054879003905583518681529351929391927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef9281900390910190a35060016102df565b5060005b9392505050565b600160a060020a031660009081526001602052604090205490565b3360009081526001602052604081205482116103845733600081815260016020908152604080832080548790039055600160a060020a03871680845292819020805487019055805186815290519293927fddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef929181900390910190a35060016101f4565b5060006101f4565b600160a060020a0391821660009081526002602090815260408083209390941682529190915220549056fea265627a7a72315820e4ae92c4517e475d9f77ac0bfcd10463a09239f34734fc0ab4d7966450fb428464736f6c63430005100032", + "hashes": + { + "allowance(address,address)": "dd62ed3e", + "approve(address,uint256)": "095ea7b3", + "balanceOf(address)": "70a08231", + "totalSupply()": "18160ddd", + "transfer(address,uint256)": "a9059cbb", + "transferFrom(address,address,uint256)": "23b872dd" + } + } + }, + "version": "0.5.16+commit.9c3226ce.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.json b/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.json new file mode 100644 index 000000000..87891db72 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.json @@ -0,0 +1,18 @@ +{ + "contracts": + { + "kv.sol:KV": + { + "bin": "608060405234801561001057600080fd5b5060405161052338038061052383398101604081905261002f91610044565b3360009081526020819052604090205561005d565b60006020828403121561005657600080fd5b5051919050565b6104b78061006c6000396000f3fe608060405234801561001057600080fd5b5060043610610073577c01000000000000000000000000000000000000000000000000000000006000350463239b83eb81146100785780633825d828146100a0578063beabacc8146100b3578063c2bc2efc146100c6578063cfc9c3f9146100fd575b600080fd5b61008b610086366004610359565b610110565b60405190151581526020015b60405180910390f35b61008b6100ae3660046103a4565b6101d6565b61008b6100c13660046103ce565b61021e565b6100ef6100d436600461040a565b600160a060020a031660009081526020819052604090205490565b604051908152602001610097565b61008b61010b3660046103ce565b6102a3565b600160a060020a0384166000908152602081905260408120548281111561015f57600160a060020a0386166000908152602081905260408120805485929061015990849061045b565b90915550505b61032081101561019c57600160a060020a0385166000908152602081905260408120805485929061019190849061046e565b909155506101ca9050565b600160a060020a038416600090815260208190526040812080548592906101c490849061046e565b90915550505b50600195945050505050565b600160a060020a0382166000908152602081905260408120546101f9818461046e565b600160a060020a03851660009081526020819052604090205550600190505b92915050565b600160a060020a03831660009081526020819052604081205482101561026c57600160a060020a0384166000908152602081905260408120805484929061026690849061045b565b90915550505b600160a060020a0383166000908152602081905260408120805484929061029490849061046e565b90915550600195945050505050565b3360009081526020819052604081208054908390836102c2838561045b565b909155505061032081101561030457600160a060020a038516600090815260208190526040812080548592906102f990849061046e565b909155506103329050565b600160a060020a0384166000908152602081905260408120805485929061032c90849061046e565b90915550505b506001949350505050565b8035600160a060020a038116811461035457600080fd5b919050565b6000806000806080858703121561036f57600080fd5b6103788561033d565b93506103866020860161033d565b92506103946040860161033d565b9396929550929360600135925050565b600080604083850312156103b757600080fd5b6103c08361033d565b946020939093013593505050565b6000806000606084860312156103e357600080fd5b6103ec8461033d565b92506103fa6020850161033d565b9150604084013590509250925092565b60006020828403121561041c57600080fd5b6104258261033d565b9392505050565b7f4e487b7100000000000000000000000000000000000000000000000000000000600052601160045260246000fd5b818103818111156102185761021861042c565b808201808211156102185761021861042c56fea2646970667358221220cfe0598bf227525b3a229778738f57a355eb5f0f171527891e3413c58bbffbd964736f6c63430008130033", + "hashes": + { + "get(address)": "c2bc2efc", + "set(address,uint256)": "3825d828", + "transfer(address,address,uint256)": "beabacc8", + "transferif(address,address,address,uint256)": "239b83eb", + "transferto(address,address,uint256)": "cfc9c3f9" + } + } + }, + "version": "0.8.19+commit.7dd6d404.Linux.g++" +} diff --git a/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.sol b/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.sol new file mode 100644 index 000000000..f5bfed426 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/test_data/kv.sol @@ -0,0 +1,59 @@ +pragma solidity >= 0.5.0; + +// Transfer tokens from the contract owner +contract KV { + mapping (address => uint256) balances; + mapping (address => uint256) allow; + + constructor(uint256 s) public { + balances[msg.sender] = s; + } + + // Get the account balance of another account with address _owner + function get(address _owner) public view returns (uint256) { + return balances[_owner]; + } + + // Send _value amount of tokens to address _to + function set(address _to, uint256 _value) public returns (bool) { + uint256 values = balances[_to]; + balances[_to] = _value + values; + return true; + } + + // Send _value amount of tokens to address _to + function transfer(address _from, address _to, uint256 _value) public returns (bool) { + if (balances[_from] > _value) { + balances[_from] -= _value; + } + balances[_to] += _value; + return true; + } + + function transferif(address _from, address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[_from]; + + if (balances[_from] > _value) { + balances[_from] -= _value; + } + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + + function transferto(address _to1, address _to2, uint256 _value) public returns (bool) { + uint256 value = balances[msg.sender]; + balances[msg.sender] -= _value; + if (value < 800 ) { + balances[_to1] += _value; + } else { + balances[_to2] += _value; + } + return true; + } + +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/utils.h b/platform/consensus/ordering/fides/executor/x_manager/utils.h new file mode 100644 index 000000000..2d243d66f --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/utils.h @@ -0,0 +1,36 @@ +/* + * 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 "eEVM/address.h" + +namespace resdb { +namespace contract { + +typedef eevm::Address Address; + +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/v_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/v_controller.cpp new file mode 100644 index 000000000..d5a363c32 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/v_controller.cpp @@ -0,0 +1,87 @@ +#include "service/contract/executor/x_manager/v_controller.h" + +#include + +namespace resdb { +namespace contract { +namespace x_manager { + +VController::VController(DataStorage * storage) : ConcurrencyController(storage){ + + changes_list_.resize(window_size_); + for(int i = 0; i < window_size_; ++i){ + changes_list_[i].clear(); + } +} + +VController::~VController(){} + +const ConcurrencyController::ModifyMap * VController::GetChangeList(int64_t commit_id) const { + return &changes_list_[commit_id]; +} + +void VController::PushCommit(int64_t commit_id, const ModifyMap& local_changes) { + changes_list_[commit_id] = local_changes; +} + +bool VController::CheckCommit(int64_t commit_id) { + const auto& change_set = changes_list_[commit_id]; + if(change_set.empty()){ + LOG(ERROR)<<" no commit id record found:"<GetVersion(address); + //LOG(ERROR)<<"get version:"< new_commit_ids; + for(const auto& it : change_set){ + bool done = false; + for(int i = it.second.size()-1; i >=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + break; + case STORE: + //LOG(ERROR)<<"commit:"<Store(it.first, op.data); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first); + done = true; + break; + } + } + } + return true; + +} + +} +} +} // namespace eevm diff --git a/platform/consensus/ordering/fides/executor/x_manager/v_controller.h b/platform/consensus/ordering/fides/executor/x_manager/v_controller.h new file mode 100644 index 000000000..db8b71c8d --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/v_controller.h @@ -0,0 +1,33 @@ +#pragma once + +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class VController : public ConcurrencyController { + public: + VController(DataStorage * storage); + ~VController(); + + virtual void PushCommit(int64_t commit_id, const ModifyMap& local_changes_); + bool Commit(int64_t commit_id); + const ModifyMap * GetChangeList(int64_t commit_id) const; + +private: +bool CheckCommit(int64_t commit_id); + private: + const int window_size_ = 2000; + std::vector changes_list_; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_committer.cpp b/platform/consensus/ordering/fides/executor/x_manager/x_committer.cpp new file mode 100644 index 000000000..e9b388112 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_committer.cpp @@ -0,0 +1,257 @@ +/* + * 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 "service/contract/executor/x_manager/x_committer.h" +#include "service/contract/executor/x_manager/executor_state.h" + +#include +#include + +#include "common/utils/utils.h" +#include "eEVM/exception.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +//#define Debug + +namespace resdb { +namespace contract { +namespace x_manager { + +namespace { + +class TimeTrack { +public: + TimeTrack(std::string name = ""){ + name_ = name; + start_time_ = GetCurrentTime(); + } + + ~TimeTrack(){ + uint64_t end_time = GetCurrentTime(); + //LOG(ERROR) << name_ <<" run:" << (end_time - start_time_)<<"ms"; + } + + double GetRunTime(){ + uint64_t end_time = GetCurrentTime(); + return (end_time - start_time_) / 1000000.0; + } +private: + std::string name_; + uint64_t start_time_; +}; + +} + +XCommitter:: XCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num):gs_(global_state), + worker_num_(worker_num), + window_size_(window_size) { + + //LOG(ERROR)<<"init window:"<(storage, window_size); + //controller_ = std::make_unique(storage, window_size*2); + executor_ = std::make_unique(); + + resp_list_.resize(window_size_); + is_done_.resize(window_size_); + for(int i = 0; i < window_size_;++i){ + is_done_[i] =false; + resp_list_[i] = nullptr; + } + + //context_list_.resize(window_size_); + //tmp_resp_list_.resize(window_size_); + first_id_ = 0; + last_id_ = 1; + is_stop_ = false; + id_ = 1; + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request_ptr = request_queue_.Pop(); + if (request_ptr == nullptr) { + continue; + } + ExecutionContext * request = *request_ptr; + + TimeTrack track; + ExecutorState executor_state(controller_.get(), request->GetContractExecuteInfo()->commit_id); + executor_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id, + request->RedoTime()); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &executor_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time=request->RedoTime(); + } + resp->runtime = track.GetRunTime()*1000; + } + else { + //LOG(ERROR)<<"commit :"<commit_id<<" fail"; + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + + +XCommitter::~XCommitter(){ + //LOG(ERROR)<<"desp"; + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + +void XCommitter::AddTask(int64_t commit_id, std::unique_ptr context){ + context_list_[commit_id] = std::move(context); +} + +ExecutionContext* XCommitter::GetTaskContext(int64_t commit_id){ + return context_list_[commit_id].get(); +} + +void XCommitter::AsyncExecContract(std::vector& requests) { + + return ; +} + +std::vector> XCommitter::ExecContract( + std::vector& requests) { + + controller_->Clear(); + int id = 1; + std::vector> tmp_resp_list; + for(auto& request: requests) { + request.commit_id = id++; + auto context = std::make_unique(request); + auto context_ptr = context.get(); + context_ptr->start_time = GetCurrentTime(); + + AddTask(request.commit_id, std::move(context)); + request_queue_.Push(std::make_unique(context_ptr)); + tmp_resp_list.push_back(nullptr); + } + + tmp_resp_list.push_back(nullptr); + std::vector> resp_list; + + int process_num = id-1; + //LOG(ERROR)<<"wait num:"<0) { + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + + int64_t resp_id= resp->commit_id; + int ret = resp->ret; + #ifdef Debug + LOG(ERROR)<<"resp:"<Commit(resp_id); + if(!commit_ret){ + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + const auto& redo_list = controller_->GetRedo(); + for(int64_t id : redo_list) { + controller_->Clear(id); + auto context_ptr = GetTaskContext(id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo:"<(context_ptr)); + } + + const auto& done_list = controller_->GetDone(); + for(int id : done_list){ + //tmp_resp_list[id]->rws = *controller_->GetChangeList(id); + tmp_resp_list[id]->delay = controller_->GetDelay(id)/1000.0; + //LOG(ERROR)<<"done :"<rws.size(); + resp_list.push_back(std::move(tmp_resp_list[id])); + process_num--; + continue; + } + //resp_list.push_back(std::move(tmp_resp_list[resp_id])); + //process_num--; + //continue; + } + else { + controller_->Clear(resp_id); + auto context_ptr = GetTaskContext(resp_id); + context_ptr->SetRedo(); + #ifdef Debug + LOG(ERROR)<<"redo :"<(context_ptr)); + } + } + return resp_list; +// LOG(ERROR)<<"last id:"< XCommitter::ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state) { + //LOG(ERROR)<<"start:"<ExecContract(caller_address, contract_address, func_addr, func_param, state); +} + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_committer.h b/platform/consensus/ordering/fides/executor/x_manager/x_committer.h new file mode 100644 index 000000000..041e0b5d6 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_committer.h @@ -0,0 +1,107 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/contract_executor.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/streaming_e_controller.h" +#include "service/contract/executor/x_manager/x_controller.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class XCommitter : public ContractCommitter { + public: + XCommitter( + DataStorage * storage, + GlobalState * global_state, + int window_size, + int worker_num = 2); + + ~XCommitter(); + + //void SetExecuteCallBack(std::function)> ) override; + + void AsyncExecContract(std::vector& request) override; + + absl::StatusOr ExecContract( + const Address& caller_address, const Address& contract_address, + const std::string& func_addr, + const Params& func_param, EVMState * state); + + std::vector> ExecContract(std::vector& execute_info); + + void SetController(std::unique_ptr controller); + +private: + void AddTask(int64_t commit_id, std::unique_ptr comtext); + void RemoveTask(int64_t commit_id); + void ResponseProcess(); + ExecutionContext* GetTaskContext(int64_t commit_id); + + bool WaitNext(); + bool WaitAll(); + + void CallBack(uint64_t commit_id); + + private: + std::unique_ptr controller_; + //std::unique_ptr controller_; + std::unique_ptr executor_; + GlobalState* gs_; + std::vector workers_; + std::thread response_; + std::atomic is_stop_; + std::atomic first_id_, last_id_, id_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + std::vector> resp_list_; + std::vector is_done_; + + std::map> context_list_; + + const int worker_num_; + int window_size_; + std::function)> call_back_; + std::condition_variable cv_; + std::mutex mutex_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_controller.cpp b/platform/consensus/ordering/fides/executor/x_manager/x_controller.cpp new file mode 100644 index 000000000..38d6b06e9 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_controller.cpp @@ -0,0 +1,1758 @@ +#include "service/contract/executor/x_manager/x_controller.h" + +#include +#include +#include + +#include "eEVM/exception.h" + +#include "common/utils/utils.h" + +//#define CDebug +//#define DDebug + +namespace resdb { +namespace contract { +namespace x_manager { + +int GetHashKey(const uint256_t& address){ + + const uint8_t* bytes = intx::as_bytes(address); + //uint8_t arr[32] = {}; + //memset(arr,0,sizeof(arr)); + //intx::be::store(arr, address); + //const uint8_t* bytes = arr; + size_t sz = sizeof(address); + int v = 0; + for(int i = 0; i < sz; ++i){ + v += bytes[i]; + } + return v%2048; + } + + +XController::XController(DataStorage * storage, int window_size) : + ConcurrencyController(storage), window_size_(window_size), storage_(storage){ + for(int i = 0; i < 32; i++){ + if((1<window_size_){ + window_size_ = (1< XController::GetChangeList(int64_t commit_id){ + // read lock + //return std::move(changes_list_[commit_id]); + return nullptr; +} + +void XController::Clear(int64_t commit_id){ +#ifdef CDebug + LOG(ERROR)<<"CLEAR id:"<& change_set, const Data& data){ + if(change_set.empty()){ + change_set.push_back(data); + } + else if(data.state != LOAD){ + if(change_set.back().state != LOAD){ + change_set.pop_back(); + } + change_set.push_back(data); + } + assert(change_set.size()<3); +} + +int XController::GetLastR(const uint256_t& address, int addr_idx){ + #ifdef CDebug + LOG(ERROR)<<"get last r, lp size:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"<second; + if(lp.empty()){ + //LOG(ERROR)<<"no last:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< XController::GetReach(int from){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + for(int ch : child_[x]){ + if(v.find(ch) == v.end()){ + v.insert(ch); + q.push(ch); + } + } + } + //assert(reach_[to][from]==false); + return v; +} + +std::set XController::GetReach(int from, int to){ + std::queue q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + if(x==to) { + //LOG(ERROR)<<"access from:"< q; + q.push(from); + + std::set v; + v.insert(from); + + while(!q.empty()){ + int x = q.front(); + q.pop(); + LOG(ERROR)<<"reach from:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"< r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle + #ifdef CDebug + LOG(ERROR)<<"have cycle after change:"< new_f; + std::vector new_cf; + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + #ifdef CDebug + LOG(ERROR)<<"connect idx:"<second){ + if(f==0){ + int addr_idx_e = addr_idx&window_size_; + auto& mp = root_addr_[addr_idx_e][address]; + int sz = mp.size(); + //LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(cf == last_idx){ + continue; + } + if(committed_[cf]){ + continue; + } + new_cf.push_back(cf); + #ifdef CDebug + LOG(ERROR)<<"address:"<1){ + for(int cf : mp){ + if(committed_[cf]){ + continue; + } + if(cf == last_idx){ + continue; + } + + //assert(cf != last_idx); + new_cf.push_back(cf); + // LOG(ERROR)<<"address:"<second.erase(ait->second.find(f)); + } + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + if(!new_cf.empty()){ + std::set r = GetReach(last_idx); + for(int cf: new_cf){ + if(r.find(cf) != r.end()){ + // cycle +#ifdef CDebug + LOG(ERROR)<<"have cycle after change:"<second.insert(cf); + addr_child_[cf][address].insert(last_idx); + addr_pre_[last_idx][address].insert(cf); + child_[cf].insert(last_idx); + pre_[last_idx].insert(cf); + } + } + + pre_[idx].insert(last_idx); + child_[last_idx].insert(idx); + #ifdef CDebug + LOG(ERROR)<<"connect :"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_id, data.state!=STORE); + //assert(ret.second<=1); + } + else { + data.version = addr_changes_list_[last_r][addr_id].data.version+1; + bool ret = Connect(commit_id, last_r, address, addr_id, false); + if(!ret){ + LOG(ERROR)<<"connect commit:"<Load(address, false); + //LOG(ERROR)<<"load storage:"<Load(address, false); + // data.data = ret.first; + // data.version = ret.second; + bool ret = Connect(commit_id, 0, address, addr_id, data.state!=STORE); + assert(ret); + // assert(ret.second<=1); + } + else { + { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_id].data.data; + data.version = addr_changes_list_[last_w][addr_id].data.version; + } + } + } + } + + //AddDataToChangeList(changes_list_[commit_id][address], data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"< q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty() && x != commit_id){ + if(cs.back().state == STORE){ + return x; + } + } + for(int f : addr_pre_[x][address]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + +int XController::FindR(int64_t commit_id, const uint256_t& address, int addr_idx) { + + std::queue q; + q.push(commit_id); + + std::vector v(window_size_+1); + for(int i = 0; i <= window_size_; i++) v[i] = 0; + v[commit_id] = 1; + + while(!q.empty()){ + int x = q.front(); + q.pop(); + auto& cs = changes_list_[x][address]; + if(!cs.empty()){ + if(cs.front().state == LOAD){ + return x; + } + } + for(int f : pre_[x]) { + if(v[f]==0){ + v[f] = 1; + q.push(f); + } + } + } + return -1; +} + + +bool XController::AddOldNode(const uint256_t& address, int addr_idx, + int64_t commit_id, Data& data) { + + #ifdef CDebug + //LOG(ERROR)<<"add old node address:"<Load(address, false); + data.version = ret.second+1; + */ + data.version = 0; + Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_r)){ + //LOG(ERROR)<<"commit id:"<Load(address, false); + data.data = ret.first; + data.version = ret.second; + } + bool ret = Connect(commit_id, 0, address, addr_idx, data.state!=STORE); + assert(ret); + //assert(ret.second==0); + } + else { + if(Reach(commit_id, last_w)){ + //assert(1==0); + //std::set rs = GetReach(commit_id, last_w); + // cycle? + #ifdef CDebug + // LOG(ERROR)<<"commit id:"<0){ + data.data = addr_changes_list_[last_w][addr_idx].data.data; + data.version = addr_changes_list_[last_w][addr_idx].data.version; + } + } + } + } + else { + if(data.state == STORE){ + if(change_set.back().state == LOAD){ + data.version = change_set.back().version+1; + } + else { + data.version = change_set.back().version; + } + } + else { + data.data = change_set.back().data; + data.version = change_set.back().version; + } + if(data.state == STORE){ + ConnectSelf(commit_id, commit_id, address, addr_idx); + if(aborted_[commit_id]){ + AbortNode(commit_id); + return false; + } + has_w = has_write_[commit_id]; + } + auto& lrp = lastr_[addr_idx]; + auto& lwp = lastw_[addr_idx]; + + if(lrp.find(commit_id) != lrp.end()){ + if(data.state == STORE){ + lwp.insert(commit_id); + } + } + else if( lwp.find(commit_id) != lwp.end()){ + if(data.state == LOAD){ + lrp.insert(commit_id); + } + } + } +#ifdef CDebug +LOG(ERROR)<<"has w:"<0 && data.state == STORE){ + AbortNodeFrom(commit_id, address); + } + } + +// AddDataToChangeList(change_set, data); + #ifdef CDebug + //LOG(ERROR)<<"add commit id:"<"; + PrintReach(i,j); + PrintReach(j,i); + assert(1==0); + } + } + } + #endif + /* + for(int i = 1; i < 500; ++i){ + for(auto it : addr_pre_[i]){ + auto address = it.first; + for(int c : it.second){ + if(c==0)continue; + if(addr_child_[c][address].find(i) == addr_child_[c][address].end()){ + LOG(ERROR)<<" id:"<GetVersion(it.first, false); + if(op.state == STORE){ + /* + if(op.version+1 != v){ + LOG(ERROR)<<"state:"<0){ + if(pre_[commit_id].size()==1 && *pre_[commit_id].begin() == 0){ + } + else { + #ifdef CDebug + for(int f : pre_[commit_id]){ + LOG(ERROR)<<" wait for pre:"<=0 && !done;--i){ + const auto& op = it.second[i]; + switch(op.state){ + case LOAD: + //LOG(ERROR)<<"load"; + break; + case STORE: + //LOG(ERROR)<<"commit:"<StoreWithVersion(it.first, op.data, op.version, false); + done = true; + break; + case REMOVE: + //LOG(ERROR)<<"remove:"<Remove(it.first, false); + done = true; + break; + } + } + } + + //LOG(ERROR)<<" commit id done:"< &XController::GetRedo(){ + return redo_; +} +const std::vector &XController::GetDone(){ + return done_; +} + +uint64_t XController::GetDelay(int64_t commit_id) { + return commit_delay_[commit_id]; +} + +} +} +} + diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_controller.h b/platform/consensus/ordering/fides/executor/x_manager/x_controller.h new file mode 100644 index 000000000..495e92f66 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_controller.h @@ -0,0 +1,141 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +#include "service/contract/executor/x_manager/concurrency_controller.h" +#include "service/contract/executor/x_manager/data_storage.h" +#include "platform/common/queue/lock_free_queue.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class XController : public ConcurrencyController { + public: + XController(DataStorage * storage, int window_size); + virtual ~XController(); + + // ============================== + virtual void Store(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + virtual uint256_t Load(const int64_t commit_id, const uint256_t& key, int version); + bool Remove(const int64_t commit_id, const uint256_t& key, int version); + + + // ============================== + typedef std::map > CommitList; + + const std::vector& GetDone(); + const std::vector &GetRedo(); + + void PushCommit(int64_t commit_id, const ModifyMap& local_changes_){} + bool Commit(int64_t commit_id); + + void StoreInternal(const int64_t commit_id, const uint256_t& key, const uint256_t& value, int version); + uint256_t LoadInternal(const int64_t commit_id, const uint256_t& key, int version); + + std::unique_ptr GetChangeList(int64_t commit_id); + + void Clear(); +void Clear(int64_t commit_id); + uint64_t GetDelay(int64_t commit_id); + + private: + + bool CommitUpdates(int64_t commit_id); + + bool CheckCommit(int64_t commit_id); + + void AppendPreRecord(const uint256_t& address, + int64_t commit_id, Data& data); + + int GetLastR(const uint256_t& address, int addr_id); + int GetLastW(const uint256_t& address, int addr_id); + int GetLastWOnly(const uint256_t& address, int addr_id); + int FindW(int64_t commit_id, const uint256_t& address, int addr_id); + int FindR(int64_t commit_id, const uint256_t& address, int addr_id); + + bool Connect(int idx, int last_idx, const uint256_t& address, int addr_idx, bool is_read); + void ConnectSelf(int idx, int last_idx, const uint256_t& address, int addr_idx); + bool AddNewNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + bool AddOldNode(const uint256_t& address, int addr_idx, int64_t commit_id, Data& data); + void AddDataToChangeList(std::vector& change_set, const Data& data); + + void RecursiveAbort(int64_t idx, const uint256_t& address); + void AbortNodeFrom(int64_t idx, const uint256_t& address); + void AbortNode(int64_t idx); + bool ContainRead(int commit_id, const uint256_t &address); + + void RemoveNode(int64_t commit_id); + + void Abort(const int64_t commit_id); + bool Reach(int from, int to); + bool PrintReach(int from, int to); + std::set GetReach(int from, int to); + std::set GetReach(int from); + + int GetDep(int from, int to); + void AddRedo(int64_t commit_id); + + int AddressToId(const uint256_t& key); + uint256_t& GetAddress(int key); + + void ResetReach(int64_t commit_id); +void Update(const std::vector& commit_id) ; + private: + int window_size_ = 1000; + + struct DataInfo { + int64_t commit_id; + int type; + Data data; + int version; + DataInfo():type(0){} + DataInfo(int64_t commit_id, int type, Data&data) : commit_id(commit_id), type(type), data(data){} + }; + + //typedef std::map>> KeyMap; + std::vector changes_list_; + std::vector> addr_changes_list_; + //std::vector> changes_list_; + + std::set pd_; + std::vector redo_; + std::vector done_; + DataStorage* storage_; + std::vector wait_; + std::mutex mutex_[2048], abort_mutex_; + std::mutex g_mutex_, k_mutex_[2048]; + std::map > lock_[2048]; + bool aborted_[2048]; + bool is_redo_[2048]; + bool finish_[2048]; + bool committed_[2048]; + bool has_write_[2048]; + std::vector abort_list_; + std::set pre_[2048]; + std::set child_[2048]; + std::bitset<2048> reach_[2048]; + + std::map> addr_pre_[2048]; + std::map> addr_child_[2048]; + std::map> root_addr_[2048]; + std::unordered_map > lastr_, lastw_; + + std::unordered_map> check_; + std::set check_abort_; + std::vector post_abort_, pending_check_; + std::map key_[2048]; + std::unordered_map akey_; + std::atomic key_id_; + bool ds_ = false; + uint64_t commit_time_[2048], commit_delay_[2048]; +}; + +} +} +} // namespace resdb diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_verifier.cpp b/platform/consensus/ordering/fides/executor/x_manager/x_verifier.cpp new file mode 100644 index 000000000..7dc04c22e --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_verifier.cpp @@ -0,0 +1,213 @@ +/* + * 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 "service/contract/executor/x_manager/x_verifier.h" + +#include + +#include "service/contract/executor/x_manager/local_state.h" +#include "common/utils/utils.h" + +#include "glog/logging.h" +#include "eEVM/processor.h" + +namespace resdb { +namespace contract { +namespace x_manager { + + +XVerifier:: XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num):storage_(storage), gs_(global_state),worker_num_(worker_num) { + + controller_ = std::make_unique(storage); + is_stop_ = false; + + for (int i = 0; i < worker_num_; ++i) { + workers_.push_back(std::thread([&]() { + while (!is_stop_) { + auto request = request_queue_.Pop(); + if (request == nullptr) { + continue; + } + + LocalState local_state(controller_.get()); + local_state.Set(gs_->GetAccount( + request->GetContractExecuteInfo()->contract_address), + request->GetContractExecuteInfo()->commit_id); + + std::unique_ptr resp = std::make_unique(); + auto ret = ExecContract(request->GetContractExecuteInfo()->caller_address, + request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->func_addr, + request->GetContractExecuteInfo()->func_params, &local_state); + resp->state = ret.status(); + resp->contract_address = request->GetContractExecuteInfo()->contract_address; + resp->commit_id = request->GetContractExecuteInfo()->commit_id; + resp->user_id = request->GetContractExecuteInfo()->user_id; + if(ret.ok()){ + resp->ret = 0; + resp->result = *ret; + if(request->IsRedo()){ + resp->retry_time++; + } + local_state.Flesh(request->GetContractExecuteInfo()->contract_address, + request->GetContractExecuteInfo()->commit_id); + } + else { + resp->ret = -1; + } + resp_queue_.Push(std::move(resp)); + } + })); + } +} + +XVerifier::~XVerifier(){ + is_stop_ = true; + for (int i = 0; i < worker_num_; ++i) { + workers_[i].join(); + } +} + + +bool XVerifier::VerifyContract( + const std::vector& request_list, + const std::vector& rws_list) { + std::vector d; + std::map > g; + std::map id; + + for(int i = 0; i < rws_list.size();++i){ + d.push_back(0); + for(auto it : rws_list[i]){ + const Address& address = it.first; + //LOG(ERROR)<<"i ="< q; + for(int i = 0; i < request_list.size();++i){ + if(d[i]==0){ + auto context = std::make_unique(request_list[i]); + context->GetContractExecuteInfo()->commit_id = i; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add:"<0){ + auto resp = resp_queue_.Pop(); + if(resp == nullptr){ + continue; + } + process_num--; + int resp_commit_id = resp->commit_id; + bool ret = controller_->Commit(resp_commit_id); + if(!ret){ + return false; + } + //LOG(ERROR)<<"verify:"<GetChangeList(resp_commit_id); + if(!RWSEqual(*rws, rws_list[resp_commit_id])){ + LOG(ERROR)<<"rws not equal"; + return false; + } + for(int n_id : g[resp_commit_id]){ + //LOG(ERROR)<<"next id:"<(request_list[n_id]); + context->GetContractExecuteInfo()->commit_id = n_id; + request_queue_.Push(std::move(context)); + //LOG(ERROR)<<"add next:"<first != it2->first){ + LOG(ERROR)<<"address not equal:"<first<<" "<first; + return false; + } + +/* + if(it1->second.size() != it2->second.size()){ + LOG(ERROR)<<"item size not equal:"<second.size()<<" "<second.size()<<" address:"<first; + return false; + } + */ + + int last2 = -1, last1 = -1; + for(int i = it2->second.size()-1; i >=0; i--){ + if(it2->second[i].state == STORE){ + last2 = i; + break; + } + } + + for(int i = it1->second.size()-1; i >=0; i--){ + if(it1->second[i].state == STORE){ + last1 = i; + break; + } + } + + if(last2 ==-1 && last1 == -1){ + continue; + } + if(last2 >=0 && last1 >=0){ + if(it1->second[last1].data != it2->second[last2].data){ + LOG(ERROR)<<"data not equal:"<first<<" data:"<second[last1].data<<" "<second[last2].data; + return false; + } + continue; + } + LOG(ERROR)<<"write set not equal:"<first; + } + return true; +} + +} +} // namespace contract +} // namespace resdb + diff --git a/platform/consensus/ordering/fides/executor/x_manager/x_verifier.h b/platform/consensus/ordering/fides/executor/x_manager/x_verifier.h new file mode 100644 index 000000000..3f0261351 --- /dev/null +++ b/platform/consensus/ordering/fides/executor/x_manager/x_verifier.h @@ -0,0 +1,74 @@ +/* + * 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 "absl/status/statusor.h" +#include "eEVM/opcode.h" +#include "platform/common/queue/lock_free_queue.h" +#include "service/contract/executor/x_manager/global_state.h" +#include "service/contract/executor/x_manager/v_controller.h" +#include "service/contract/executor/x_manager/contract_committer.h" +#include "service/contract/executor/x_manager/committer_context.h" +#include "service/contract/executor/x_manager/contract_verifier.h" +#include "service/contract/executor/x_manager/utils.h" +#include "service/contract/proto/func_params.pb.h" + +namespace resdb { +namespace contract { +namespace x_manager { + +class XVerifier : public ContractVerifier { + public: + XVerifier( + DataStorage * storage, + GlobalState * global_state, int worker_num = 2); + + ~XVerifier(); + + bool VerifyContract( + const std::vector& request_list, + const std::vector& rws_list); + + bool RWSEqual(const ConcurrencyController :: ModifyMap&a, const ConcurrencyController :: ModifyMap&b); + + private: + DataStorage * storage_; + GlobalState* gs_; + std::vector workers_; + std::unique_ptr controller_; + std::atomic is_stop_; + + LockFreeQueue request_queue_; + LockFreeQueue resp_queue_; + const int worker_num_; +}; + +} +} // namespace contract +} // namespace resdb diff --git a/platform/consensus/ordering/fides/framework/BUILD b/platform/consensus/ordering/fides/framework/BUILD new file mode 100644 index 000000000..c8311d90b --- /dev/null +++ b/platform/consensus/ordering/fides/framework/BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//visibility:private"]) + +cc_library( + name = "consensus", + srcs = ["consensus.cpp"], + hdrs = ["consensus.h"], + visibility = [ + "//visibility:public", + ], + copts = ["-Iexternal/openenclave"], + deps = [ + "//common/utils", + "//platform/consensus/ordering/common/framework:consensus", + "//platform/consensus/ordering/fides/algorithm:fides", + "//enclave:headers", + ], +) + diff --git a/platform/consensus/ordering/fides/framework/consensus.cpp b/platform/consensus/ordering/fides/framework/consensus.cpp new file mode 100644 index 000000000..17ad1c125 --- /dev/null +++ b/platform/consensus/ordering/fides/framework/consensus.cpp @@ -0,0 +1,131 @@ +/* + * 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 "platform/consensus/ordering/fides/framework/consensus.h" + +#include +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace fides { + +FidesConsensus::FidesConsensus(const ResDBConfig& config, + std::unique_ptr executor, + oe_enclave_t* enclave) + : Consensus(config, std::move(executor)) { + int total_replicas = config_.GetReplicaNum(); + int f = (total_replicas - 1) / 3; + + Init(); + + if (config_.GetPublicKeyCertificateInfo() + .public_key() + .public_key_info() + .type() != CertificateKeyInfo::CLIENT) { + fides_ = std::make_unique(config_.GetSelfInfo().id(), f, + total_replicas, GetSignatureVerifier(), + config, enclave); + + fides_->SetSingleCallFunc( + [&](int type, const google::protobuf::Message& msg, int node_id) { + return SendMsg(type, msg, node_id); + }); + + fides_->SetBroadcastCallFunc( + [&](int type, const google::protobuf::Message& msg) { + return Broadcast(type, msg); + }); + + fides_->SetCommitFunc([&](const google::protobuf::Message& msg) { + return CommitMsg(dynamic_cast(msg)); + }); + } + LOG(ERROR)<<"init consensus done"; +} + +int FidesConsensus::ProcessCustomConsensus(std::unique_ptr request) { + //LOG(ERROR)<<"recv request:"<user_type()); + // int64_t current_time = GetCurrentTime(); + if (request->user_type() == MessageType::NewBlock) { + std::unique_ptr p = std::make_unique(); + if (!p->ParseFromString(request->data())) { + LOG(ERROR) << "parse proposal fail"; + assert(1 == 0); + return -1; + } + fides_->ReceiveBlock(std::move(p)); + } + // Only 1 type of message: NewBlock. + /* + else if (request->user_type() == MessageType::BlockACK) { + std::unique_ptr metadata = std::make_unique(); + if (!metadata->ParseFromString(request->data())) { + LOG(ERROR) << "parse proposal fail"; + assert(1 == 0); + return -1; + } + fides_->ReceiveBlockACK(std::move(metadata)); + } else if (request->user_type() == MessageType::Cert) { + std::unique_ptr cert = std::make_unique(); + if (!cert->ParseFromString(request->data())) { + LOG(ERROR) << "parse proposal fail"; + assert(1 == 0); + return -1; + } + fides_->ReceiveBlockCert(std::move(cert)); + } + */ + return 0; +} + +int FidesConsensus::ProcessNewTransaction(std::unique_ptr request) { + std::unique_ptr txn = std::make_unique(); + txn->set_data(request->data()); + txn->set_hash(request->hash()); + txn->set_proxy_id(request->proxy_id()); + return fides_->ReceiveTransaction(std::move(txn)); +} + +int FidesConsensus::CommitMsg(const google::protobuf::Message& msg) { + return CommitMsgInternal(dynamic_cast(msg)); +} + +int FidesConsensus::CommitMsgInternal(const Transaction& txn) { + //LOG(ERROR)<<"commit txn:"< request = std::make_unique(); + request->set_queuing_time(txn.queuing_time()); + request->set_data(txn.data()); + request->set_seq(txn.id()); + request->set_proxy_id(txn.proxy_id()); + transaction_executor_->AddExecuteMessage(std::move(request)); + return 0; +} + + + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/framework/consensus.h b/platform/consensus/ordering/fides/framework/consensus.h new file mode 100644 index 000000000..46b56b9c6 --- /dev/null +++ b/platform/consensus/ordering/fides/framework/consensus.h @@ -0,0 +1,55 @@ +/* + * 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 "executor/common/transaction_manager.h" +#include "platform/consensus/execution/transaction_executor.h" +#include "platform/consensus/ordering/common/framework/consensus.h" +#include "platform/consensus/ordering/fides/algorithm/fides.h" +#include "platform/networkstrate/consensus_manager.h" +#include "enclave/sgx_cpp_u.h" + +namespace resdb { +namespace fides { + +class FidesConsensus : public common::Consensus { + public: + FidesConsensus(const ResDBConfig& config, + std::unique_ptr transaction_manager, + oe_enclave_t* enclave); + + protected: + int ProcessCustomConsensus(std::unique_ptr request) override; + int ProcessNewTransaction(std::unique_ptr request) override; + int CommitMsg(const google::protobuf::Message& msg) override; + int CommitMsgInternal(const Transaction& txn); + + private: + std::unique_ptr fides_; +}; + +} // namespace fides +} // namespace resdb diff --git a/platform/consensus/ordering/fides/proto/BUILD b/platform/consensus/ordering/fides/proto/BUILD new file mode 100644 index 000000000..9b640b702 --- /dev/null +++ b/platform/consensus/ordering/fides/proto/BUILD @@ -0,0 +1,18 @@ +package(default_visibility = ["//platform/consensus/ordering/fides:__subpackages__"]) + +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_proto_grpc//python:defs.bzl", "python_proto_library") + +proto_library( + name = "proposal_proto", + srcs = ["proposal.proto"], + deps = [ + "//common/proto:signature_info_proto", + ], +) + +cc_proto_library( + name = "proposal_cc_proto", + deps = [":proposal_proto"], +) diff --git a/platform/consensus/ordering/fides/proto/proposal.proto b/platform/consensus/ordering/fides/proto/proposal.proto new file mode 100644 index 000000000..57cdd8937 --- /dev/null +++ b/platform/consensus/ordering/fides/proto/proposal.proto @@ -0,0 +1,85 @@ + +syntax = "proto3"; +import "common/proto/signature_info.proto"; + +package resdb.fides; + +message Metadata { + int32 round = 1; + bytes hash = 2; + int32 sender = 3; + SignatureInfo sign = 4; // signature for each resp. + int32 proposer = 5; +} + +message Reference { + int32 round = 1; + bytes hash = 2; + int32 proposer = 3; +} + +message CertLink { + repeated Certificate cert = 1; +} + +message ReferenceLink { + repeated ReferenceLink ref = 1; +} + +message Certificate { + repeated Metadata metadata = 1; + int32 round = 2; + int32 proposer = 3; + bytes hash = 4; + CertLink strong_cert = 5; + CounterInfo counter = 6; +} + +message Transaction{ + int32 id = 1; + bytes data = 2; + bytes hash = 3; + int32 proxy_id = 4; + int64 create_time = 5; + int32 group_id = 6; + int64 queuing_time = 7; +} + +enum ProposalType{ + NewMsg = 0; + Prepared = 1; + Commit = 2; + Ready_execute = 3; +}; + +message Header { + int32 proposer_id = 3; + int32 proposal_id = 4; + int64 create_time = 6; + ProposalType status = 7; + int32 round = 8; + CertLink strong_cert = 9; + CertLink weak_cert = 10; + CounterInfo counter = 11; +} + +message Proposal { + Header header = 1; + repeated Transaction transactions = 3; + int32 sender = 5; + bytes hash = 6; + int64 queuing_time = 7; +}; + +message CounterInfo { + uint32 value = 1; + bytes attestation = 2; +} + +enum MessageType { + NewProposal = 0; + NewBlock = 1; + BlockACK = 2; + Cert = 3; +}; + diff --git a/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.cpp b/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.cpp index 4317a3960..fc3d7fc5e 100644 --- a/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.cpp +++ b/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.h" diff --git a/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.h b/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.h index 9cb3edafd..42fbf3ae7 100644 --- a/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.h +++ b/platform/consensus/ordering/geo_pbft/consensus_manager_geo_pbft.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.cpp b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.cpp index acc10b5f2..61c5cf37e 100644 --- a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.cpp +++ b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h" diff --git a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h index c059da5e2..43fc7c76d 100644 --- a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h +++ b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment_test.cpp b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment_test.cpp index 6cc66585b..0c9c31c45 100644 --- a/platform/consensus/ordering/geo_pbft/geo_pbft_commitment_test.cpp +++ b/platform/consensus/ordering/geo_pbft/geo_pbft_commitment_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/geo_pbft/geo_pbft_commitment.h" diff --git a/platform/consensus/ordering/geo_pbft/hash_set.h b/platform/consensus/ordering/geo_pbft/hash_set.h index 2ee556bbb..e608e7955 100644 --- a/platform/consensus/ordering/geo_pbft/hash_set.h +++ b/platform/consensus/ordering/geo_pbft/hash_set.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once 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..ef7483083 100644 --- a/platform/consensus/ordering/pbft/checkpoint_manager.cpp +++ b/platform/consensus/ordering/pbft/checkpoint_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/checkpoint_manager.h" @@ -37,7 +31,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 +65,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 +99,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 +165,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..e043978ea 100644 --- a/platform/consensus/ordering/pbft/checkpoint_manager.h +++ b/platform/consensus/ordering/pbft/checkpoint_manager.h @@ -1,33 +1,27 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once #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 +41,7 @@ class CheckPointManager : public CheckPoint { SignatureVerifier* verifier); virtual ~CheckPointManager(); - TxnMemoryDB* GetTxnDB(); + ChainState* GetTxnDB(); uint64_t GetMaxTxnSeq(); void AddCommitData(std::unique_ptr request); @@ -92,7 +86,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/checkpoint_manager_test.cpp b/platform/consensus/ordering/pbft/checkpoint_manager_test.cpp index d5ef6528f..f302e8195 100644 --- a/platform/consensus/ordering/pbft/checkpoint_manager_test.cpp +++ b/platform/consensus/ordering/pbft/checkpoint_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/checkpoint_manager.h" diff --git a/platform/consensus/ordering/pbft/commitment.cpp b/platform/consensus/ordering/pbft/commitment.cpp index d30add520..c2e84b149 100644 --- a/platform/consensus/ordering/pbft/commitment.cpp +++ b/platform/consensus/ordering/pbft/commitment.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/commitment.h" @@ -160,7 +154,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/commitment.h b/platform/consensus/ordering/pbft/commitment.h index d694caaa8..03d77cf3d 100644 --- a/platform/consensus/ordering/pbft/commitment.h +++ b/platform/consensus/ordering/pbft/commitment.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/commitment_test.cpp b/platform/consensus/ordering/pbft/commitment_test.cpp index 86e16927a..1b8b24a79 100644 --- a/platform/consensus/ordering/pbft/commitment_test.cpp +++ b/platform/consensus/ordering/pbft/commitment_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/commitment.h" diff --git a/platform/consensus/ordering/pbft/consensus_manager_pbft.cpp b/platform/consensus/ordering/pbft/consensus_manager_pbft.cpp index f1946bbe2..5131445a2 100644 --- a/platform/consensus/ordering/pbft/consensus_manager_pbft.cpp +++ b/platform/consensus/ordering/pbft/consensus_manager_pbft.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/consensus_manager_pbft.h" diff --git a/platform/consensus/ordering/pbft/consensus_manager_pbft.h b/platform/consensus/ordering/pbft/consensus_manager_pbft.h index 5cefee33b..bea50990c 100644 --- a/platform/consensus/ordering/pbft/consensus_manager_pbft.h +++ b/platform/consensus/ordering/pbft/consensus_manager_pbft.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/lock_free_collector_pool.cpp b/platform/consensus/ordering/pbft/lock_free_collector_pool.cpp index 6ccc86723..4336b66d2 100644 --- a/platform/consensus/ordering/pbft/lock_free_collector_pool.cpp +++ b/platform/consensus/ordering/pbft/lock_free_collector_pool.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/lock_free_collector_pool.h" diff --git a/platform/consensus/ordering/pbft/lock_free_collector_pool.h b/platform/consensus/ordering/pbft/lock_free_collector_pool.h index 0c075e2a1..5335c4fd6 100644 --- a/platform/consensus/ordering/pbft/lock_free_collector_pool.h +++ b/platform/consensus/ordering/pbft/lock_free_collector_pool.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/lock_free_collector_pool_test.cpp b/platform/consensus/ordering/pbft/lock_free_collector_pool_test.cpp index 89e51867c..1c4d5761c 100644 --- a/platform/consensus/ordering/pbft/lock_free_collector_pool_test.cpp +++ b/platform/consensus/ordering/pbft/lock_free_collector_pool_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/lock_free_collector_pool.h" diff --git a/platform/consensus/ordering/pbft/message_manager.cpp b/platform/consensus/ordering/pbft/message_manager.cpp index b7b7bfe2a..37d3b5de8 100644 --- a/platform/consensus/ordering/pbft/message_manager.cpp +++ b/platform/consensus/ordering/pbft/message_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/message_manager.h" diff --git a/platform/consensus/ordering/pbft/message_manager.h b/platform/consensus/ordering/pbft/message_manager.h index 667dfe831..c51fd22a5 100644 --- a/platform/consensus/ordering/pbft/message_manager.h +++ b/platform/consensus/ordering/pbft/message_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -32,7 +26,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 +124,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/ordering/pbft/mock_checkpoint_manager.h b/platform/consensus/ordering/pbft/mock_checkpoint_manager.h index d52c22ee4..0cdd6028d 100644 --- a/platform/consensus/ordering/pbft/mock_checkpoint_manager.h +++ b/platform/consensus/ordering/pbft/mock_checkpoint_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/performance_manager.cpp b/platform/consensus/ordering/pbft/performance_manager.cpp index dfecb5940..a5ba979ff 100644 --- a/platform/consensus/ordering/pbft/performance_manager.cpp +++ b/platform/consensus/ordering/pbft/performance_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/performance_manager.h" diff --git a/platform/consensus/ordering/pbft/performance_manager.h b/platform/consensus/ordering/pbft/performance_manager.h index cfc6fd97b..7fc6e5967 100644 --- a/platform/consensus/ordering/pbft/performance_manager.h +++ b/platform/consensus/ordering/pbft/performance_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/pre_very_consensus_service_pbft.h b/platform/consensus/ordering/pbft/pre_very_consensus_service_pbft.h index e03852606..1b26313de 100644 --- a/platform/consensus/ordering/pbft/pre_very_consensus_service_pbft.h +++ b/platform/consensus/ordering/pbft/pre_very_consensus_service_pbft.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/query.cpp b/platform/consensus/ordering/pbft/query.cpp index f01cc2344..72bb4ed5b 100644 --- a/platform/consensus/ordering/pbft/query.cpp +++ b/platform/consensus/ordering/pbft/query.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/query.h" diff --git a/platform/consensus/ordering/pbft/query.h b/platform/consensus/ordering/pbft/query.h index afc6734f6..e451cdfeb 100644 --- a/platform/consensus/ordering/pbft/query.h +++ b/platform/consensus/ordering/pbft/query.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/query_test.cpp b/platform/consensus/ordering/pbft/query_test.cpp index 54d9c6088..4cc1a1175 100644 --- a/platform/consensus/ordering/pbft/query_test.cpp +++ b/platform/consensus/ordering/pbft/query_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/query.h" diff --git a/platform/consensus/ordering/pbft/response_manager.cpp b/platform/consensus/ordering/pbft/response_manager.cpp index 5764d96b3..9d212f619 100644 --- a/platform/consensus/ordering/pbft/response_manager.cpp +++ b/platform/consensus/ordering/pbft/response_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/response_manager.h" diff --git a/platform/consensus/ordering/pbft/response_manager.h b/platform/consensus/ordering/pbft/response_manager.h index 8b08c7fad..fdf89b752 100644 --- a/platform/consensus/ordering/pbft/response_manager.h +++ b/platform/consensus/ordering/pbft/response_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/response_manager_test.cpp b/platform/consensus/ordering/pbft/response_manager_test.cpp index 655b4aafe..62b16ee6f 100644 --- a/platform/consensus/ordering/pbft/response_manager_test.cpp +++ b/platform/consensus/ordering/pbft/response_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/response_manager.h" diff --git a/platform/consensus/ordering/pbft/transaction_collector.cpp b/platform/consensus/ordering/pbft/transaction_collector.cpp index 4447dfc53..0c69c401d 100644 --- a/platform/consensus/ordering/pbft/transaction_collector.cpp +++ b/platform/consensus/ordering/pbft/transaction_collector.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/transaction_collector.h" diff --git a/platform/consensus/ordering/pbft/transaction_collector.h b/platform/consensus/ordering/pbft/transaction_collector.h index 601ecbf1f..a3edaa44e 100644 --- a/platform/consensus/ordering/pbft/transaction_collector.h +++ b/platform/consensus/ordering/pbft/transaction_collector.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/transaction_collector_test.cpp b/platform/consensus/ordering/pbft/transaction_collector_test.cpp index d105082de..d1c9c7eed 100644 --- a/platform/consensus/ordering/pbft/transaction_collector_test.cpp +++ b/platform/consensus/ordering/pbft/transaction_collector_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/transaction_collector.h" diff --git a/platform/consensus/ordering/pbft/transaction_utils.cpp b/platform/consensus/ordering/pbft/transaction_utils.cpp index d5a69a3f6..5369abd02 100644 --- a/platform/consensus/ordering/pbft/transaction_utils.cpp +++ b/platform/consensus/ordering/pbft/transaction_utils.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/transaction_utils.h" diff --git a/platform/consensus/ordering/pbft/transaction_utils.h b/platform/consensus/ordering/pbft/transaction_utils.h index fbed78396..e5e3eac22 100644 --- a/platform/consensus/ordering/pbft/transaction_utils.h +++ b/platform/consensus/ordering/pbft/transaction_utils.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/viewchange_manager.cpp b/platform/consensus/ordering/pbft/viewchange_manager.cpp index 79569baf2..7033801b3 100644 --- a/platform/consensus/ordering/pbft/viewchange_manager.cpp +++ b/platform/consensus/ordering/pbft/viewchange_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/viewchange_manager.h" diff --git a/platform/consensus/ordering/pbft/viewchange_manager.h b/platform/consensus/ordering/pbft/viewchange_manager.h index e65eeaa18..a6e085edd 100644 --- a/platform/consensus/ordering/pbft/viewchange_manager.h +++ b/platform/consensus/ordering/pbft/viewchange_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/ordering/pbft/viewchange_manager_test.cpp b/platform/consensus/ordering/pbft/viewchange_manager_test.cpp index 83eec0013..94701535b 100644 --- a/platform/consensus/ordering/pbft/viewchange_manager_test.cpp +++ b/platform/consensus/ordering/pbft/viewchange_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/ordering/pbft/viewchange_manager.h" diff --git a/platform/consensus/ordering/poe/algorithm/BUILD b/platform/consensus/ordering/poe/algorithm/BUILD new file mode 100644 index 000000000..357f56d8b --- /dev/null +++ b/platform/consensus/ordering/poe/algorithm/BUILD @@ -0,0 +1,15 @@ +package(default_visibility = ["//platform/consensus/ordering/poe:__subpackages__"]) + +cc_library( + name = "poe", + srcs = ["poe.cpp"], + hdrs = ["poe.h"], + deps = [ + "//platform/statistic:stats", + "//common:comm", + "//platform/consensus/ordering/poe/proto:proposal_cc_proto", + "//common/crypto:signature_verifier", + "//platform/consensus/ordering/common/algorithm:protocol_base", + "//platform/common/queue:lock_free_queue", + ], +) diff --git a/platform/consensus/ordering/poe/algorithm/poe.cpp b/platform/consensus/ordering/poe/algorithm/poe.cpp new file mode 100644 index 000000000..14580b7d5 --- /dev/null +++ b/platform/consensus/ordering/poe/algorithm/poe.cpp @@ -0,0 +1,80 @@ +#include "platform/consensus/ordering/poe/algorithm/poe.h" + +#include + +#include "common/crypto/signature_verifier.h" +#include "common/utils/utils.h" + +namespace resdb { +namespace poe { + +PoE::PoE(int id, int f, int total_num, SignatureVerifier* verifier) + : ProtocolBase(id, f, total_num), verifier_(verifier) { + + LOG(ERROR) << "get proposal graph"; + id_ = id; + total_num_ = total_num; + f_ = f; + is_stop_ = false; + seq_ = 0; +} + +PoE::~PoE() { + is_stop_ = true; +} + +bool PoE::IsStop() { return is_stop_; } + +bool PoE::ReceiveTransaction(std::unique_ptr txn) { + // LOG(ERROR)<<"recv txn:"; + txn->set_create_time(GetCurrentTime()); + txn->set_seq(seq_++); + txn->set_proposer(id_); + + Broadcast(MessageType::Propose, *txn); + return true; +} + +bool PoE::ReceivePropose(std::unique_ptr txn) { + std::string hash = txn->hash(); + int64_t seq = txn->seq(); + int proposer = txn->proposer(); + { + // LOG(ERROR)<<"recv proposal"; + //LOG(ERROR)<<"recv txn from:"<proposer()<<" id:"<seq(); + std::unique_lock lk(mutex_); + data_[txn->hash()]=std::move(txn); + } + + Proposal proposal; + proposal.set_hash(hash); + proposal.set_seq(seq); + proposal.set_proposer(id_); + Broadcast(MessageType::Prepare, proposal); + //LOG(ERROR)<<"receive proposal done"; + return true; +} + +bool PoE::ReceivePrepare(std::unique_ptr proposal) { + std::unique_ptr txn = nullptr; + { + //LOG(ERROR)<<"recv proposal from:"<proposer()<<" id:"<seq(); + std::unique_lock lk(mutex_); + received_[proposal->hash()].insert(proposal->proposer()); + auto it = data_.find(proposal->hash()); + if(it != data_.end()){ + if(received_[proposal->hash()].size()>=2*f_+1){ + txn = std::move(it->second); + data_.erase(it); + } + } + } + if(txn != nullptr){ + commit_(*txn); + } + // LOG(ERROR)<<"receive proposal done"; + return true; +} + +} // namespace poe +} // namespace resdb diff --git a/platform/consensus/ordering/poe/algorithm/poe.h b/platform/consensus/ordering/poe/algorithm/poe.h new file mode 100644 index 000000000..20cc71a93 --- /dev/null +++ b/platform/consensus/ordering/poe/algorithm/poe.h @@ -0,0 +1,40 @@ +#pragma once + +#include +#include +#include +#include + +#include "platform/common/queue/lock_free_queue.h" +#include "platform/consensus/ordering/common/algorithm/protocol_base.h" +#include "platform/consensus/ordering/poe/proto/proposal.pb.h" +#include "platform/statistic/stats.h" + +namespace resdb { +namespace poe { + +class PoE: public common::ProtocolBase { + public: + PoE(int id, int f, int total_num, SignatureVerifier* verifier); + ~PoE(); + + bool ReceiveTransaction(std::unique_ptr txn); + bool ReceivePropose(std::unique_ptr txn); + bool ReceivePrepare(std::unique_ptr proposal); + + private: + bool IsStop(); + + private: + std::mutex mutex_; + std::map > received_; + std::map > data_; + + int64_t seq_; + bool is_stop_; + SignatureVerifier* verifier_; + Stats* global_stats_; +}; + +} // namespace cassandra +} // namespace resdb diff --git a/platform/consensus/ordering/poe/framework/BUILD b/platform/consensus/ordering/poe/framework/BUILD new file mode 100644 index 000000000..7030d2a0d --- /dev/null +++ b/platform/consensus/ordering/poe/framework/BUILD @@ -0,0 +1,16 @@ +package(default_visibility = ["//visibility:private"]) + +cc_library( + name = "consensus", + srcs = ["consensus.cpp"], + hdrs = ["consensus.h"], + visibility = [ + "//visibility:public", + ], + deps = [ + "//common/utils", + "//platform/consensus/ordering/common/framework:consensus", + "//platform/consensus/ordering/poe/algorithm:poe", + ], +) + diff --git a/platform/consensus/ordering/poe/framework/consensus.cpp b/platform/consensus/ordering/poe/framework/consensus.cpp new file mode 100644 index 000000000..c162b7fce --- /dev/null +++ b/platform/consensus/ordering/poe/framework/consensus.cpp @@ -0,0 +1,108 @@ +/* + * 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 "platform/consensus/ordering/poe/framework/consensus.h" + +#include +#include + +#include "common/utils/utils.h" + +namespace resdb { +namespace poe { + +Consensus::Consensus(const ResDBConfig& config, + std::unique_ptr executor) + : common::Consensus(config, std::move(executor)){ + int total_replicas = config_.GetReplicaNum(); + int f = (total_replicas - 1) / 3; + + Init(); + + start_ = 0; + + if (config_.GetPublicKeyCertificateInfo() + .public_key() + .public_key_info() + .type() != CertificateKeyInfo::CLIENT) { + poe_ = std::make_unique( + config_.GetSelfInfo().id(), f, + total_replicas, GetSignatureVerifier()); + InitProtocol(poe_.get()); + } +} + +int Consensus::ProcessCustomConsensus(std::unique_ptr request) { + //LOG(ERROR)<<"receive commit:"<type()<<" "<user_type()); + if (request->user_type() == MessageType::Propose) { + std::unique_ptr txn = std::make_unique(); + if (!txn->ParseFromString(request->data())) { + assert(1 == 0); + LOG(ERROR) << "parse proposal fail"; + return -1; + } + poe_->ReceivePropose(std::move(txn)); + return 0; + } else if (request->user_type() == MessageType::Prepare) { + std::unique_ptr proposal = std::make_unique(); + if (!proposal->ParseFromString(request->data())) { + LOG(ERROR) << "parse proposal fail"; + assert(1 == 0); + return -1; + } + poe_->ReceivePrepare(std::move(proposal)); + return 0; + } + return 0; +} + +int Consensus::ProcessNewTransaction(std::unique_ptr request) { + std::unique_ptr txn = std::make_unique(); + txn->set_data(request->data()); + txn->set_hash(request->hash()); + txn->set_proxy_id(request->proxy_id()); + txn->set_uid(request->uid()); + //LOG(ERROR)<<"receive txn"; + return poe_->ReceiveTransaction(std::move(txn)); +} + +int Consensus::CommitMsg(const google::protobuf::Message& msg) { + return CommitMsgInternal(dynamic_cast(msg)); +} + +int Consensus::CommitMsgInternal(const Transaction& txn) { + //LOG(ERROR)<<"commit txn:"< request = std::make_unique(); + request->set_data(txn.data()); + request->set_seq(txn.seq()); + request->set_uid(txn.uid()); + request->set_proxy_id(txn.proxy_id()); + + transaction_executor_->Commit(std::move(request)); + return 0; +} + +} // namespace poe +} // namespace resdb diff --git a/platform/consensus/ordering/poe/framework/consensus.h b/platform/consensus/ordering/poe/framework/consensus.h new file mode 100644 index 000000000..72e56e181 --- /dev/null +++ b/platform/consensus/ordering/poe/framework/consensus.h @@ -0,0 +1,59 @@ +/* + * 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 "executor/common/transaction_manager.h" +#include "platform/consensus/ordering/common/framework/consensus.h" +#include "platform/consensus/ordering/poe/algorithm/poe.h" +#include "platform/networkstrate/consensus_manager.h" + +namespace resdb { +namespace poe { + +class Consensus : public common::Consensus { + public: + Consensus(const ResDBConfig& config, + std::unique_ptr transaction_manager); + virtual ~Consensus() = default; + + private: + int ProcessCustomConsensus(std::unique_ptr request) override; + int ProcessNewTransaction(std::unique_ptr request) override; + int CommitMsg(const google::protobuf::Message& msg) override; + int CommitMsgInternal(const Transaction& txn); + + int Prepare(const Transaction& txn); + + protected: + std::unique_ptr poe_; + Stats* global_stats_; + int64_t start_; + std::mutex mutex_; + int send_num_[200]; +}; + +} // namespace cassandra +} // namespace resdb diff --git a/platform/consensus/ordering/poe/framework/consensus_test.cpp b/platform/consensus/ordering/poe/framework/consensus_test.cpp new file mode 100644 index 000000000..2c8834a8b --- /dev/null +++ b/platform/consensus/ordering/poe/framework/consensus_test.cpp @@ -0,0 +1,179 @@ +/* + * 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 "platform/consensus/ordering/cassandra/framework/consensus.h" + +#include +#include +#include + +#include + +#include "common/test/test_macros.h" +#include "executor/common/mock_transaction_manager.h" +#include "platform/config/resdb_config_utils.h" +#include "platform/networkstrate/mock_replica_communicator.h" + +namespace resdb { +namespace cassandra { +namespace { + +using ::resdb::testing::EqualsProto; +using ::testing::_; +using ::testing::Invoke; +using ::testing::Test; + +ResDBConfig GetConfig() { + ResDBConfig config({GenerateReplicaInfo(1, "127.0.0.1", 1234), + GenerateReplicaInfo(2, "127.0.0.1", 1235), + GenerateReplicaInfo(3, "127.0.0.1", 1236), + GenerateReplicaInfo(4, "127.0.0.1", 1237)}, + GenerateReplicaInfo(1, "127.0.0.1", 1234)); + return config; +} + +class ConsensusTest : public Test { + public: + ConsensusTest() : config_(GetConfig()) { + auto transaction_manager = + std::make_unique(); + mock_transaction_manager_ = transaction_manager.get(); + consensus_ = + std::make_unique(config_, std::move(transaction_manager)); + consensus_->SetCommunicator(&replica_communicator_); + } + + void AddTransaction(const std::string& data) { + auto request = std::make_unique(); + request->set_type(Request::TYPE_NEW_TXNS); + + Transaction txn; + + BatchUserRequest batch_request; + auto req = batch_request.add_user_requests(); + req->mutable_request()->set_data(data); + + batch_request.set_local_id(1); + batch_request.SerializeToString(txn.mutable_data()); + + txn.SerializeToString(request->mutable_data()); + + EXPECT_EQ(consensus_->ConsensusCommit(nullptr, std::move(request)), 0); + } + + protected: + ResDBConfig config_; + MockTransactionExecutorDataImpl* mock_transaction_manager_; + MockReplicaCommunicator replica_communicator_; + std::unique_ptr transaction_manager_; + std::unique_ptr consensus_; +}; + +TEST_F(ConsensusTest, NormalCase) { + std::promise commit_done; + std::future commit_done_future = commit_done.get_future(); + + EXPECT_CALL(replica_communicator_, BroadCast) + .WillRepeatedly(Invoke([&](const google::protobuf::Message& msg) { + Request request = *dynamic_cast(&msg); + + if (request.user_type() == MessageType::NewProposal) { + LOG(ERROR) << "bc new proposal"; + consensus_->ConsensusCommit(nullptr, + std::make_unique(request)); + LOG(ERROR) << "recv proposal done"; + } + if (request.user_type() == MessageType::Vote) { + LOG(ERROR) << "bc vote"; + + VoteMessage ack_msg; + assert(ack_msg.ParseFromString(request.data())); + for (int i = 1; i <= 3; ++i) { + ack_msg.set_proposer_id(i); + auto new_req = std::make_unique(request); + ack_msg.SerializeToString(new_req->mutable_data()); + + consensus_->ConsensusCommit(nullptr, std::move(new_req)); + } + } + // LOG(ERROR)<<"bc type:"<type()<<" user + // type:"<user_type(); + if (request.user_type() == MessageType::Prepare) { + LOG(ERROR) << "bc prepare"; + + VoteMessage ack_msg; + assert(ack_msg.ParseFromString(request.data())); + for (int i = 1; i <= 3; ++i) { + ack_msg.set_proposer_id(i); + auto new_req = std::make_unique(request); + ack_msg.SerializeToString(new_req->mutable_data()); + + consensus_->ConsensusCommit(nullptr, std::move(new_req)); + } + } + if (request.user_type() == MessageType::Voteprep) { + LOG(ERROR) << "bc voterep:"; + + VoteMessage ack_msg; + assert(ack_msg.ParseFromString(request.data())); + for (int i = 1; i <= 3; ++i) { + ack_msg.set_proposer_id(i); + auto new_req = std::make_unique(request); + ack_msg.SerializeToString(new_req->mutable_data()); + LOG(ERROR) << "new request type:" << new_req->user_type(); + + consensus_->ConsensusCommit(nullptr, std::move(new_req)); + } + } + LOG(ERROR) << "done"; + return 0; + })); + + EXPECT_CALL(*mock_transaction_manager_, ExecuteData) + .WillOnce(Invoke([&](const std::string& msg) { + LOG(ERROR) << "execute txn:" << msg; + EXPECT_EQ(msg, "transaction1"); + return nullptr; + })); + + EXPECT_CALL(replica_communicator_, SendMessage(_, 0)) + .WillRepeatedly( + Invoke([&](const google::protobuf::Message& msg, int64_t) { + Request request = *dynamic_cast(&msg); + if (request.type() == Request::TYPE_RESPONSE) { + LOG(ERROR) << "get response"; + commit_done.set_value(true); + } + return; + })); + + AddTransaction("transaction1"); + + commit_done_future.get(); +} + +} // namespace +} // namespace cassandra +} // namespace resdb diff --git a/platform/consensus/ordering/poe/proto/BUILD b/platform/consensus/ordering/poe/proto/BUILD new file mode 100644 index 000000000..8088db092 --- /dev/null +++ b/platform/consensus/ordering/poe/proto/BUILD @@ -0,0 +1,16 @@ +package(default_visibility = ["//platform/consensus/ordering/poe:__subpackages__"]) + +load("@rules_cc//cc:defs.bzl", "cc_proto_library") +load("@rules_proto//proto:defs.bzl", "proto_library") +load("@rules_proto_grpc//python:defs.bzl", "python_proto_library") + +proto_library( + name = "proposal_proto", + srcs = ["proposal.proto"], + #visibility = ["//visibility:public"], +) + +cc_proto_library( + name = "proposal_cc_proto", + deps = [":proposal_proto"], +) diff --git a/platform/consensus/ordering/poe/proto/proposal.proto b/platform/consensus/ordering/poe/proto/proposal.proto new file mode 100644 index 000000000..8302752ad --- /dev/null +++ b/platform/consensus/ordering/poe/proto/proposal.proto @@ -0,0 +1,28 @@ + +syntax = "proto3"; + +package resdb.poe; + +message Transaction{ + int32 id = 1; + bytes data = 2; + bytes hash = 3; + int32 proxy_id = 4; + int32 proposer = 5; + int64 uid = 6; + int64 create_time = 7; + int64 seq = 9; +} + +message Proposal { + bytes hash = 1; + int32 proposer = 2; + int64 seq =3 ; +} + +enum MessageType { + None = 0; + Propose = 1; + Prepare = 2; +} + diff --git a/platform/consensus/recovery/recovery.cpp b/platform/consensus/recovery/recovery.cpp index b61aa4766..fb1f6d50a 100644 --- a/platform/consensus/recovery/recovery.cpp +++ b/platform/consensus/recovery/recovery.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/recovery/recovery.h" diff --git a/platform/consensus/recovery/recovery.h b/platform/consensus/recovery/recovery.h index 743846bae..90f8fc99d 100644 --- a/platform/consensus/recovery/recovery.h +++ b/platform/consensus/recovery/recovery.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/consensus/recovery/recovery_test.cpp b/platform/consensus/recovery/recovery_test.cpp index f02cb9dff..a7cb1ef86 100644 --- a/platform/consensus/recovery/recovery_test.cpp +++ b/platform/consensus/recovery/recovery_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/consensus/recovery/recovery.h" @@ -112,7 +106,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 +147,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 +213,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 +290,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 +327,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 +409,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 +449,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/networkstrate/async_acceptor.cpp b/platform/networkstrate/async_acceptor.cpp index 0010a4a4d..387e98e53 100644 --- a/platform/networkstrate/async_acceptor.cpp +++ b/platform/networkstrate/async_acceptor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/async_acceptor.h" @@ -83,12 +77,14 @@ void AsyncAcceptor::Session::ReadDone() { delete recv_buffer_; } else { data_size_ = *reinterpret_cast(recv_buffer_); + /* if (data_size_ > 1e6) { LOG(ERROR) << "read data size:" << data_size_ << " data size:" << sizeof(data_size_) << " close socket"; Close(); return; } + */ } status_ ^= 1; recv_buffer_ = nullptr; diff --git a/platform/networkstrate/async_acceptor.h b/platform/networkstrate/async_acceptor.h index cde4893fa..26bc7ce44 100644 --- a/platform/networkstrate/async_acceptor.h +++ b/platform/networkstrate/async_acceptor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/async_acceptor_test.cpp b/platform/networkstrate/async_acceptor_test.cpp index 80ee4ba18..31d5ef3b0 100644 --- a/platform/networkstrate/async_acceptor_test.cpp +++ b/platform/networkstrate/async_acceptor_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/async_acceptor.h" diff --git a/platform/networkstrate/async_replica_client.cpp b/platform/networkstrate/async_replica_client.cpp index f71f86d96..af13b7996 100644 --- a/platform/networkstrate/async_replica_client.cpp +++ b/platform/networkstrate/async_replica_client.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/async_replica_client.h" diff --git a/platform/networkstrate/async_replica_client.h b/platform/networkstrate/async_replica_client.h index 2555de8fa..64189c984 100644 --- a/platform/networkstrate/async_replica_client.h +++ b/platform/networkstrate/async_replica_client.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/async_replica_client_test.cpp b/platform/networkstrate/async_replica_client_test.cpp index f018c0a3b..b04c1c6d0 100644 --- a/platform/networkstrate/async_replica_client_test.cpp +++ b/platform/networkstrate/async_replica_client_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/async_replica_client.h" diff --git a/platform/networkstrate/consensus_manager.cpp b/platform/networkstrate/consensus_manager.cpp index 3e1d47251..fa9acbab5 100644 --- a/platform/networkstrate/consensus_manager.cpp +++ b/platform/networkstrate/consensus_manager.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/consensus_manager.h" @@ -215,7 +209,7 @@ int ConsensusManager::ProcessHeartBeat(std::unique_ptr context, return -1; } - LOG(INFO) << "receive public size:" << hb_info.public_keys().size() + LOG(ERROR) << "receive public size:" << hb_info.public_keys().size() << " primary:" << hb_info.primary() << " version:" << hb_info.version() << " from region:" << request->region_info().region_id(); @@ -259,7 +253,7 @@ int ConsensusManager::ProcessHeartBeat(std::unique_ptr context, if (public_key.public_key_info().type() == CertificateKeyInfo::REPLICA) { replica_num++; if (!ReplicaExisted(info, replicas)) { - // AddNewReplica(info); + //AddNewReplica(info); } } else { if (!ReplicaExisted(info, clients_)) { @@ -337,6 +331,7 @@ std::unique_ptr ConsensusManager::GetReplicaClient( void ConsensusManager::AddNewReplica(const ReplicaInfo& info) {} void ConsensusManager::AddNewClient(const ReplicaInfo& info) { + std::unique_lock lk(mutex_); clients_.push_back(info); bc_client_->UpdateClientReplicas(clients_); } diff --git a/platform/networkstrate/consensus_manager.h b/platform/networkstrate/consensus_manager.h index c87d3ac6b..eb1e3ec33 100644 --- a/platform/networkstrate/consensus_manager.h +++ b/platform/networkstrate/consensus_manager.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -106,6 +100,7 @@ class ConsensusManager : public ServiceInterface { }; private: + std::mutex mutex_; std::thread heartbeat_thread_; std::atomic is_ready_ = false; std::unique_ptr bc_client_; diff --git a/platform/networkstrate/consensus_manager_test.cpp b/platform/networkstrate/consensus_manager_test.cpp index 4b2f0b523..427b3d92e 100644 --- a/platform/networkstrate/consensus_manager_test.cpp +++ b/platform/networkstrate/consensus_manager_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/consensus_manager.h" diff --git a/platform/networkstrate/mock_async_replica_client.h b/platform/networkstrate/mock_async_replica_client.h index e31f9ccea..22f39afb3 100644 --- a/platform/networkstrate/mock_async_replica_client.h +++ b/platform/networkstrate/mock_async_replica_client.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/mock_replica_communicator.h b/platform/networkstrate/mock_replica_communicator.h index 79edb009e..dbcc64618 100644 --- a/platform/networkstrate/mock_replica_communicator.h +++ b/platform/networkstrate/mock_replica_communicator.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/mock_service_interface.h b/platform/networkstrate/mock_service_interface.h index 5d8c0f2e1..c3de6b8d0 100644 --- a/platform/networkstrate/mock_service_interface.h +++ b/platform/networkstrate/mock_service_interface.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/replica_communicator.cpp b/platform/networkstrate/replica_communicator.cpp index 9a07d614c..6b7f06e63 100644 --- a/platform/networkstrate/replica_communicator.cpp +++ b/platform/networkstrate/replica_communicator.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/replica_communicator.h" @@ -48,16 +42,7 @@ ReplicaCommunicator::ReplicaCommunicator( worker_threads_.push_back(std::thread([&]() { io_service_.run(); })); } } - - /* - for (const ReplicaInfo& info : replicas) { - std::string ip = info.ip(); - int port = info.port(); - auto client = std::make_unique( - &io_service_, ip, port + (is_use_long_conn_ ? 10000 : 0), true); - client_pools_[std::make_pair(ip, port)] = std::move(client); - } - */ + LOG(ERROR)<<" tcp batch:"<SendMessage(data) == 0) { ret++; } else { LOG(ERROR) << "send to:" << replica.ip() << " fail"; } + //LOG(ERROR) << "send to:" << replica.ip()<<" done"; } return ret; } diff --git a/platform/networkstrate/replica_communicator.h b/platform/networkstrate/replica_communicator.h index eb66c4fe7..87994770d 100644 --- a/platform/networkstrate/replica_communicator.h +++ b/platform/networkstrate/replica_communicator.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -43,7 +37,7 @@ class ReplicaCommunicator { ReplicaCommunicator(const std::vector& replicas, SignatureVerifier* verifier = nullptr, bool is_use_long_conn = false, int epoll_num = 1, - int tcp_batch = 100); + int tcp_batch = 1); virtual ~ReplicaCommunicator(); // HeartBeat message is used to broadcast public keys. diff --git a/platform/networkstrate/replica_communicator_test.cpp b/platform/networkstrate/replica_communicator_test.cpp index d21815c11..51013ff87 100644 --- a/platform/networkstrate/replica_communicator_test.cpp +++ b/platform/networkstrate/replica_communicator_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/replica_communicator.h" diff --git a/platform/networkstrate/server_comm.h b/platform/networkstrate/server_comm.h index 9fd3cd579..8eb43bfef 100644 --- a/platform/networkstrate/server_comm.h +++ b/platform/networkstrate/server_comm.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/service_interface.cpp b/platform/networkstrate/service_interface.cpp index 1a00676de..9379f3c47 100644 --- a/platform/networkstrate/service_interface.cpp +++ b/platform/networkstrate/service_interface.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/service_interface.h" diff --git a/platform/networkstrate/service_interface.h b/platform/networkstrate/service_interface.h index 0e3413641..4ebc84013 100644 --- a/platform/networkstrate/service_interface.h +++ b/platform/networkstrate/service_interface.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/service_network.cpp b/platform/networkstrate/service_network.cpp index 40cc29cea..fa5d3d4d7 100644 --- a/platform/networkstrate/service_network.cpp +++ b/platform/networkstrate/service_network.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/service_network.h" @@ -52,7 +46,7 @@ ServiceNetwork::ServiceNetwork(const ResDBConfig& config, acceptor_ = std::make_unique(config, &input_queue_); async_acceptor_ = std::make_unique( - config.GetSelfInfo().ip(), config_.GetSelfInfo().port() + 10000, + "0.0.0.0", config_.GetSelfInfo().port() + 10000, config.GetInputWorkerNum(), std::bind(&ServiceNetwork::AcceptorHandler, this, std::placeholders::_1, std::placeholders::_2)); @@ -77,8 +71,7 @@ void ServiceNetwork::AcceptorHandler(const char* buffer, size_t data_len) { std::unique_ptr item = std::make_unique(); item->socket = nullptr; item->data = std::move(sub_request_info); - // LOG(ERROR) << "receve data from acceptor:" << data.is_resp()<<" data - // len:"<data->data_len; + // LOG(ERROR) << "receve data from acceptor:" << data.is_resp()<<" data len:"<data->data_len; global_stats_->ServerCall(); input_queue_.Push(std::move(item)); } diff --git a/platform/networkstrate/service_network.h b/platform/networkstrate/service_network.h index d545e2694..c3844c09c 100644 --- a/platform/networkstrate/service_network.h +++ b/platform/networkstrate/service_network.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/networkstrate/service_network_test.cpp b/platform/networkstrate/service_network_test.cpp index 4b9deac1f..c92eddb57 100644 --- a/platform/networkstrate/service_network_test.cpp +++ b/platform/networkstrate/service_network_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/networkstrate/service_network.h" 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..f9f2721cc 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. @@ -44,6 +45,9 @@ message ResConfigData{ optional int32 max_client_complaint_num = 21; optional int32 duplicate_check_frequency_useconds = 22; + +// for fides failure. + optional int32 failure_num = 23; } message ReplicaStates { diff --git a/platform/proto/resdb.proto b/platform/proto/resdb.proto index b17b12404..ddc3be558 100644 --- a/platform/proto/resdb.proto +++ b/platform/proto/resdb.proto @@ -37,8 +37,9 @@ message Request { TYPE_VIEWCHANGE = 16; TYPE_NEWVIEW= 17; TYPE_CUSTOM_QUERY = 18; + TYPE_CUSTOM_CONSENSUS = 19; - NUM_OF_TYPE = 19; // the total number of types. + NUM_OF_TYPE = 20; // the total number of types. // Used to create the collector. }; int32 type = 1; @@ -62,6 +63,14 @@ message Request { int32 primary_id = 17; repeated bytes hashs = 18; repeated uint64 seqs = 19; + int32 user_type = 20; + int64 user_seq = 21; + int64 queuing_time = 22; + int64 uid = 23; + int64 create_time = 24; + int64 commit_time = 25; + + bytes encrypted_data = 26; } // The response message containing response diff --git a/platform/rdbc/acceptor.cpp b/platform/rdbc/acceptor.cpp index 0e07a1c1e..3410674b8 100644 --- a/platform/rdbc/acceptor.cpp +++ b/platform/rdbc/acceptor.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/rdbc/acceptor.h" @@ -41,9 +35,9 @@ Acceptor::Acceptor(const ResDBConfig& config, input_queue_(input_queue) { socket_->SetRecvTimeout(1000000); // set 1s timeout. - LOG(ERROR) << "listen ip:" << config.GetSelfInfo().ip() + LOG(ERROR) << "listen ip:" << "0.0.0.0" << " port:" << config.GetSelfInfo().port(); - assert(socket_->Listen(config.GetSelfInfo().ip(), + assert(socket_->Listen("0.0.0.0", config.GetSelfInfo().port()) == 0); is_stop_ = false; global_stats_ = Stats::GetGlobalStats(); diff --git a/platform/rdbc/acceptor.h b/platform/rdbc/acceptor.h index 2ccc385e6..19ddb5700 100644 --- a/platform/rdbc/acceptor.h +++ b/platform/rdbc/acceptor.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/statistic/prometheus_handler.cpp b/platform/statistic/prometheus_handler.cpp index eacadf54a..644f6a184 100644 --- a/platform/statistic/prometheus_handler.cpp +++ b/platform/statistic/prometheus_handler.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/statistic/prometheus_handler.h" diff --git a/platform/statistic/prometheus_handler.h b/platform/statistic/prometheus_handler.h index e7ceca5d7..b3a8b4d9f 100644 --- a/platform/statistic/prometheus_handler.h +++ b/platform/statistic/prometheus_handler.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/platform/statistic/set_random_data.cpp b/platform/statistic/set_random_data.cpp index a30d2cba4..0b936f585 100644 --- a/platform/statistic/set_random_data.cpp +++ b/platform/statistic/set_random_data.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/platform/statistic/stats.cpp b/platform/statistic/stats.cpp index f14ba9b3f..f84bd37ad 100644 --- a/platform/statistic/stats.cpp +++ b/platform/statistic/stats.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "platform/statistic/stats.h" @@ -109,6 +103,65 @@ void Stats::MonitorGlobal() { uint64_t last_total_request = 0, last_total_geo_request = 0, last_geo_request = 0; uint64_t time = 0; + + + uint64_t num_transactions = 0, num_consumed_transactions = 0; + uint64_t num_transactions_time = 0, num_consumed_transactions_time = 0; + uint64_t last_num_transactions = 0, last_num_consumed_transactions = 0; + uint64_t last_num_transactions_time = 0, last_num_consumed_transactions_time = 0; + + uint64_t queuing_num = 0, queuing_time = 0; + uint64_t last_queuing_num = 0, last_queuing_time = 0; + uint64_t round_num = 0, round_time = 0; + uint64_t last_round_num = 0, last_round_time = 0; + + uint64_t commit_num = 0, commit_time = 0; + uint64_t last_commit_num = 0, last_commit_time = 0; + + uint64_t verify_num = 0, verify_time = 0; + uint64_t last_verify_num = 0, last_verify_time = 0; + + uint64_t execute_queuing_num = 0, execute_queuing_time = 0; + uint64_t last_execute_queuing_num = 0, last_execute_queuing_time = 0; + + uint64_t execute_num = 0, execute_time = 0; + uint64_t last_execute_num = 0, last_execute_time = 0; + + uint64_t commit_running_num = 0, commit_running_time = 0; + uint64_t last_commit_running_num = 0, last_commit_running_time = 0; + + uint64_t commit_delay_num = 0, commit_delay_time = 0; + uint64_t last_commit_delay_num = 0, last_commit_delay_time = 0; + + uint64_t commit_waiting_num = 0, commit_waiting_time = 0; + uint64_t last_commit_waiting_num = 0, last_commit_waiting_time = 0; + + uint64_t execute_delay_num = 0, execute_delay_time = 0; + uint64_t last_execute_delay_num = 0, last_execute_delay_time = 0; + + uint64_t execute_prepare_num = 0, execute_prepare_time = 0; + uint64_t last_execute_prepare_num = 0, last_execute_prepare_time = 0; + + uint64_t commit_interval_num = 0, commit_interval_time = 0; + uint64_t last_commit_interval_num = 0, last_commit_interval_time = 0; + + uint64_t commit_ratio_num = 0, commit_ratio_time = 0; + uint64_t last_commit_ratio_num = 0, last_commit_ratio_time = 0; + + uint64_t commit_queuing_num = 0, commit_queuing_time = 0; + uint64_t last_commit_queuing_num = 0, last_commit_queuing_time = 0; + + uint64_t commit_round_num = 0, commit_round_time = 0; + uint64_t last_commit_round_num = 0, last_commit_round_time = 0; + + uint64_t commit_txn_num = 0, commit_txn_time = 0; + uint64_t last_commit_txn_num = 0, last_commit_txn_time = 0; + + uint64_t commit_block_num = 0, commit_block_time = 0; + uint64_t last_commit_block_num = 0, last_commit_block_time = 0; + + uint64_t block_size_num = 0, block_size = 0; + uint64_t last_block_size_num = 0, last_block_size = 0; while (!stop_) { sleep(monitor_sleep_time_); @@ -136,6 +189,66 @@ void Stats::MonitorGlobal() { run_req_num = run_req_num_; run_req_run_time = run_req_run_time_; + queuing_num = queuing_num_; + queuing_time = queuing_time_; + + round_num = round_num_; + round_time = round_time_; + + commit_num = commit_num_; + commit_time = commit_time_; + + execute_queuing_num = execute_queuing_num_; + execute_queuing_time = execute_queuing_time_; + + execute_num = execute_num_; + execute_time = execute_time_; + + commit_running_num = commit_running_num_; + commit_running_time = commit_running_time_; + + commit_delay_num = commit_delay_num_; + commit_delay_time = commit_delay_time_; + + commit_waiting_num = commit_waiting_num_; + commit_waiting_time = commit_waiting_time_; + + execute_prepare_num = execute_prepare_num_; + execute_prepare_time = execute_prepare_time_; + + execute_delay_num = execute_delay_num_; + execute_delay_time = execute_delay_time_; + + commit_interval_num = commit_interval_num_; + commit_interval_time = commit_interval_time_; + + commit_ratio_num = commit_ratio_num_; + commit_ratio_time = commit_ratio_time_; + + commit_queuing_num = commit_queuing_num_; + commit_queuing_time = commit_queuing_time_; + + commit_round_num = commit_round_num_; + commit_round_time = commit_round_time_; + + commit_txn_num = commit_txn_num_; + commit_txn_time = commit_txn_time_; + + commit_block_num = commit_block_num_; + commit_block_time = commit_block_time_; + + block_size_num = block_size_num_; + block_size = block_size_; + + verify_num = verify_num_; + verify_time = verify_time_; + + num_transactions = num_transactions_; + num_consumed_transactions = num_consumed_transactions_; + + num_transactions_time = num_transactions_time_; + num_consumed_transactions_time = num_consumed_transactions_time_; + LOG(ERROR) << "=========== monitor =========\n" << "server call:" << server_call - last_server_call << " server process:" << server_process - last_server_process @@ -183,13 +296,106 @@ void Stats::MonitorGlobal() { << " " "seq fail:" << seq_fail - last_seq_fail << " time:" << time + << " " + "new transactions:" + << (num_transactions - last_num_transactions ) + << " " + "consumed transactions:" + << (num_consumed_transactions - last_num_consumed_transactions) + << " queuing latency :" + << static_cast(queuing_time - + last_queuing_time) / + (queuing_num - last_queuing_num) / 1000000.0 + << " round latency :" + << static_cast(round_time - + last_round_time) / + (round_num - last_round_num) / 1000000.0 + << " commit latency :" + << static_cast(commit_time - + last_commit_time) / + (commit_num - last_commit_num) / 1000000.0 + + << " verify latency :" + << static_cast(verify_time - + last_verify_time) / + (verify_num - last_verify_num) / 1000000.0 + + << " execute_queuing latency :" + << static_cast(execute_queuing_time - + last_execute_queuing_time) / + (execute_queuing_num - last_execute_queuing_num) / 1000000.0 + + << " execute latency :" + << static_cast(execute_time - + last_execute_time) / + (execute_num - last_execute_num) / 1000000.0 + + << " commit_queuing latency :" + << static_cast(commit_queuing_time - + last_commit_queuing_time) / + (commit_queuing_num - last_commit_queuing_num) / 1000000.0 + + << " commit_running latency :" + << static_cast(commit_running_time - + last_commit_running_time) / + (commit_running_num - last_commit_running_num) / 1000000.0 + + << " commit_delay latency :" + << static_cast(commit_delay_time - + last_commit_delay_time) / + (commit_delay_num - last_commit_delay_num) / 1000000.0 + + << " commit_waiting latency :" + << static_cast(commit_waiting_time - + last_commit_waiting_time) / + (commit_waiting_num - last_commit_waiting_num) / 1000000.0 + + << " execute_prepare latency :" + << static_cast(execute_prepare_time - + last_execute_prepare_time) / + (execute_prepare_num - last_execute_prepare_num) / 1000000.0 + + << " execute_delay latency :" + << static_cast(execute_delay_time - + last_execute_delay_time) / + (execute_delay_num - last_execute_delay_num) / 1000000.0 + + << " commit_round latency :" + << static_cast(commit_round_time - + last_commit_round_time) / + (commit_round_num - last_commit_round_num) + + << " commit_interval latency :" + << static_cast(commit_interval_time - + last_commit_interval_time) / + (commit_interval_num - last_commit_interval_num) / 1000000.0 + + << " commit_txn latency :" + << static_cast(commit_txn_time - + last_commit_txn_time) / + (commit_txn_num - last_commit_txn_num) + + << " commit_block latency :" + << static_cast(commit_block_time - + last_commit_block_time) / + (commit_block_num - last_commit_block_num) + + << " block_size latency :" + << static_cast(block_size - + last_block_size) / + (block_size_num - last_block_size_num) + + << " commit_ratio latency :" + << static_cast(commit_ratio_time - + last_commit_ratio_time) / + (commit_ratio_num - last_commit_ratio_num) / 1000000.0 << " " "\n--------------- monitor ------------"; if (run_req_num - last_run_req_num > 0) { LOG(ERROR) << " req client latency:" << static_cast(run_req_run_time - last_run_req_run_time) / - (run_req_num - last_run_req_num) / 1000000000.0; + (run_req_num - last_run_req_num) / 1000000.0; } last_seq_fail = seq_fail; @@ -215,6 +421,63 @@ void Stats::MonitorGlobal() { last_total_request = total_request; last_total_geo_request = total_geo_request; last_geo_request = geo_request; + + last_num_transactions = num_transactions; + last_num_consumed_transactions = num_consumed_transactions; + + last_num_transactions_time = num_transactions_time; + last_num_consumed_transactions_time = num_consumed_transactions_time; + + last_queuing_num = queuing_num; + last_queuing_time = queuing_time; + + last_round_num = round_num; + last_round_time = round_time; + + last_commit_num = commit_num; + last_commit_time = commit_time; + + last_execute_queuing_num = execute_queuing_num; + last_execute_queuing_time = execute_queuing_time; + + last_execute_num = execute_num; + last_execute_time = execute_time; + + last_commit_running_num = commit_running_num; + last_commit_running_time = commit_running_time; + + last_commit_delay_num = commit_delay_num; + last_commit_delay_time = commit_delay_time; + + last_commit_waiting_num = commit_waiting_num; + last_commit_waiting_time = commit_waiting_time; + + last_execute_delay_num = execute_delay_num; + last_execute_delay_time = execute_delay_time; + + last_execute_prepare_num = execute_prepare_num; + last_execute_prepare_time = execute_prepare_time; + + last_commit_interval_num = commit_interval_num; + last_commit_interval_time = commit_interval_time; + + last_commit_ratio_num = commit_ratio_num; + last_commit_ratio_time = commit_ratio_time; + + last_commit_queuing_num = commit_queuing_num; + last_commit_queuing_time = commit_queuing_time; + + last_commit_round_num = commit_round_num; + last_commit_round_time = commit_round_time; + + last_commit_txn_num = commit_txn_num; + last_commit_txn_time = commit_txn_time; + + last_commit_block_num = commit_block_num; + last_commit_block_time = commit_block_time; + + last_verify_num = verify_num; + last_verify_time = verify_time; } } @@ -302,6 +565,14 @@ void Stats::ServerProcess() { server_process_++; } +void Stats::AddNewTransactions(int num) { + num_transactions_++; +} + +void Stats::ConsumeTransactions(int num) { + num_consumed_transactions_++; +} + void Stats::SeqGap(uint64_t seq_gap) { seq_gap_ = seq_gap; } void Stats::AddLatency(uint64_t run_time) { @@ -309,6 +580,97 @@ void Stats::AddLatency(uint64_t run_time) { run_req_run_time_ += run_time; } +void Stats::AddQueuingLatency(uint64_t run_time) { + queuing_num_++; + queuing_time_ += run_time; +} + +void Stats::AddRoundLatency(uint64_t run_time) { + round_num_++; + round_time_ += run_time; +} + +void Stats::AddCommitLatency(uint64_t run_time) { + commit_num_++; + commit_time_ += run_time; +} + +void Stats::AddVerifyLatency(uint64_t run_time) { + verify_num_++; + verify_time_ += run_time; +} + +void Stats::AddExecuteQueuingLatency(uint64_t run_time) { + execute_queuing_num_++; + execute_queuing_time_ += run_time; +} + +void Stats::AddExecuteLatency(uint64_t run_time) { + execute_num_++; + execute_time_ += run_time; +} + +void Stats::AddCommitQueuingLatency(uint64_t run_time) { + commit_queuing_num_++; + commit_queuing_time_ += run_time; +} + +void Stats::AddCommitRuntime(uint64_t run_time) { + commit_running_num_++; + commit_running_time_ += run_time; +} + +void Stats::AddCommitWaitingLatency(uint64_t run_time) { + commit_waiting_num_++; + commit_waiting_time_ += run_time; +} + +void Stats::AddCommitDelay(uint64_t run_time) { + commit_delay_num_++; + commit_delay_time_ += run_time; +} + +void Stats::AddExecutePrepareDelay(uint64_t run_time) { + execute_prepare_num_++; + execute_prepare_time_ += run_time; +} + +void Stats::AddCommitRoundLatency(uint64_t run_time) { + //LOG(ERROR)<<"commit round:"<(prometheus_address); } diff --git a/platform/statistic/stats.h b/platform/statistic/stats.h index cc95f2bcc..a07cbc207 100644 --- a/platform/statistic/stats.h +++ b/platform/statistic/stats.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once @@ -39,6 +33,24 @@ class Stats { void Stop(); void AddLatency(uint64_t run_time); + void AddQueuingLatency(uint64_t run_time); + void AddRoundLatency(uint64_t run_time); + void AddCommitLatency(uint64_t run_time); + void AddCommitQueuingLatency(uint64_t run_time); + void AddVerifyLatency(uint64_t run_time); + void AddExecuteQueuingLatency(uint64_t run_time); + void AddExecuteLatency(uint64_t run_time); + void AddCommitRuntime(uint64_t run_time); + void AddCommitRoundLatency(uint64_t run_time); + void AddCommitWaitingLatency(uint64_t run_time); + void AddCommitDelay(uint64_t run_time); + void AddExecutePrepareDelay(uint64_t run_time); + void AddCommitInterval(uint64_t run_time); + void AddCommitTxn(int num); + void AddCommitBlock(int num); + void AddBlockSize(int size); + void AddCommitRatio(uint64_t num); + void AddExecuteDelay(uint64_t run_time); void Monitor(); void MonitorGlobal(); @@ -69,6 +81,9 @@ class Stats { void ServerProcess(); void SetPrometheus(const std::string& prometheus_address); + void AddNewTransactions(int num); + void ConsumeTransactions(int num); + protected: Stats(int sleep_time = 5); ~Stats(); @@ -96,9 +111,26 @@ class Stats { std::atomic run_req_run_time_; std::atomic seq_gap_; std::atomic total_request_, total_geo_request_, geo_request_; + std::atomic num_transactions_, num_transactions_time_, num_consumed_transactions_, num_consumed_transactions_time_; + std::atomic queuing_num_, queuing_time_, round_num_, round_time_, commit_num_, commit_time_; + std::atomic execute_queuing_num_, execute_queuing_time_, verify_num_, verify_time_; + std::atomic execute_num_, execute_time_; + std::atomic commit_running_num_, commit_running_time_; + std::atomic commit_queuing_num_, commit_queuing_time_; + std::atomic commit_round_num_, commit_round_time_; + std::atomic commit_txn_num_, commit_txn_time_; + std::atomic commit_block_num_, commit_block_time_; + std::atomic commit_delay_num_, commit_delay_time_; + std::atomic commit_waiting_num_, commit_waiting_time_; + std::atomic execute_prepare_num_, execute_prepare_time_; + std::atomic commit_interval_num_, commit_interval_time_; + std::atomic block_size_num_, block_size_; + std::atomic commit_ratio_num_, commit_ratio_time_; + std::atomic execute_delay_num_, execute_delay_time_; int monitor_sleep_time_ = 5; // default 5s. std::unique_ptr prometheus_; + }; } // namespace resdb diff --git a/platform/test/resdb_test.cpp b/platform/test/resdb_test.cpp index 0152a0803..fca03a152 100644 --- a/platform/test/resdb_test.cpp +++ b/platform/test/resdb_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include @@ -109,7 +103,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 ce0d1d260..f07523fe8 100644 --- a/proto/kv/kv.proto +++ b/proto/kv/kv.proto @@ -7,16 +7,47 @@ message KVRequest { NONE = 0; SET = 1; GET = 2; - GETVALUES = 3; + 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/scripts/deploy/README.md b/scripts/deploy/README.md index 585831cfd..bf8e54e31 100644 --- a/scripts/deploy/README.md +++ b/scripts/deploy/README.md @@ -4,7 +4,8 @@ This directory includes deployment scripts that help to deploy ResilientDB on mu ## Deploy KV Service -Add the IP addresses and the SSH key of the machines where you wish to deploy ResilientDB replicas and client proxy in the file [config/kv_server.conf](https://github.com/msadoghi/nexres/blob/master/deploy/config/kv_server.conf). +Add the IP addresses of the machines where you wish to deploy ResilientDB replicas and client proxy in the file [config/kv_server.conf](config/kv_server.conf). +Create the ssh key file in the config "config/key.conf" and put your ssh key there (See the [key_example.conf](config/key_example.conf) as an example). We recommend using private IP addresses of each machine. * If you do not require any SSH key to log in to a machine, then you would need to update the scripts. diff --git a/scripts/deploy/config/cassandra.config b/scripts/deploy/config/cassandra.config new file mode 100644 index 000000000..d452da24e --- /dev/null +++ b/scripts/deploy/config/cassandra.config @@ -0,0 +1,10 @@ +{ + "clientBatchNum": 1000, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 64, + "worker_num": 2, + "input_worker_num": 1, + "output_worker_num": 5 +} diff --git a/scripts/deploy/config/fair.config b/scripts/deploy/config/fair.config new file mode 100644 index 000000000..e2f76f257 --- /dev/null +++ b/scripts/deploy/config/fair.config @@ -0,0 +1,10 @@ +{ + "clientBatchNum": 1000, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 256, + "worker_num": 2, + "input_worker_num": 1, + "output_worker_num": 5 +} diff --git a/scripts/deploy/config/fides.config b/scripts/deploy/config/fides.config new file mode 100644 index 000000000..f940544e7 --- /dev/null +++ b/scripts/deploy/config/fides.config @@ -0,0 +1,11 @@ +{ + "clientBatchNum": 200, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 384, + "worker_num": 10, + "input_worker_num": 1, + "output_worker_num": 5, + "failure_num": 1 +} \ No newline at end of file diff --git a/scripts/deploy/config/kv_performance_server.conf b/scripts/deploy/config/kv_performance_server.conf index ef0a21cbc..7816268fc 100644 --- a/scripts/deploy/config/kv_performance_server.conf +++ b/scripts/deploy/config/kv_performance_server.conf @@ -1,9 +1,12 @@ iplist=( -172.31.23.110 -172.31.31.183 -172.31.22.246 -172.31.26.117 -172.31.21.196 +172.17.0.2 +172.17.0.3 +172.17.0.4 +172.17.0.5 +172.17.0.6 +172.17.0.7 +172.17.0.8 +172.17.0.9 ) -key=~/.ssh/junchao.pem +client_num=4 diff --git a/scripts/deploy/config/kv_performance_server_128.conf b/scripts/deploy/config/kv_performance_server_128.conf new file mode 100644 index 000000000..38109f207 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_128.conf @@ -0,0 +1,262 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +172.31.21.137 +172.31.16.140 +172.31.26.139 +172.31.21.141 +172.31.27.140 +172.31.24.142 +172.31.31.142 +172.31.31.146 +172.31.19.145 +172.31.23.149 +172.31.30.149 +172.31.22.218 +172.31.17.214 +172.31.30.221 +172.31.24.220 +172.31.29.162 +172.31.24.221 +172.31.19.167 +172.31.21.166 +172.31.27.178 +172.31.18.177 +172.31.17.183 +172.31.21.179 +172.31.26.129 +172.31.16.190 +172.31.27.133 +172.31.29.133 +172.31.19.250 +172.31.27.250 +172.31.16.251 +172.31.26.250 +172.31.29.254 +172.31.21.252 +172.31.25.199 +172.31.23.195 +172.31.25.200 +172.31.28.199 +172.31.19.203 +172.31.28.201 +172.31.23.204 +172.31.26.203 +172.31.18.213 +172.31.19.208 +172.31.27.27 +172.31.17.24 +172.31.21.30 +172.31.25.29 +172.31.29.31 +172.31.21.54 +172.31.22.53 +172.31.27.60 +172.31.23.56 +172.31.25.63 +172.31.30.61 +172.31.27.2 +172.31.28.0 +172.31.26.4 +172.31.31.3 +172.31.20.8 +172.31.24.6 +172.31.23.21 +172.31.26.11 +172.31.29.23 +172.31.31.21 +172.31.25.82 +172.31.20.81 +172.31.25.87 +172.31.27.85 +172.31.26.93 +172.31.16.90 +172.31.24.94 +172.31.26.94 +172.31.19.95 +172.31.17.94 +172.31.30.44 +172.31.21.33 +172.31.26.47 +172.31.27.47 +172.31.26.50 +172.31.22.49 +172.31.19.122 +172.31.31.121 +172.31.24.127 +172.31.24.126 +172.31.21.65 +172.31.26.65 +172.31.29.70 +172.31.22.66 +172.31.19.72 +172.31.26.70 +172.31.22.74 +172.31.23.72 +172.31.27.76 +172.31.24.75 +172.31.19.81 +172.31.16.81 +172.31.47.249 +172.31.36.247 +172.31.40.246 +172.31.38.242 +172.31.46.241 +172.31.43.240 +172.31.34.239 +172.31.39.239 +172.31.33.198 +172.31.35.197 +172.31.39.194 +172.31.44.252 +172.31.42.252 +172.31.35.251 +172.31.47.251 +172.31.36.250 +172.31.35.223 +172.31.39.222 +172.31.44.219 +172.31.34.219 +172.31.32.217 +172.31.36.216 +172.31.32.212 +172.31.33.208 +172.31.39.186 +172.31.32.185 +172.31.38.183 +172.31.32.174 +172.31.41.172 +172.31.43.168 +172.31.39.167 +172.31.32.160 +172.31.43.146 +172.31.32.145 +172.31.33.135 +172.31.37.134 +172.31.42.132 +172.31.32.190 +172.31.36.190 +172.31.36.188 +172.31.36.97 +172.31.39.97 +172.31.35.97 +172.31.43.156 +172.31.47.155 +172.31.40.150 +172.31.44.149 +172.31.47.148 +172.31.36.111 +172.31.43.108 +172.31.39.107 +172.31.45.106 +172.31.34.104 +172.31.37.104 +172.31.37.102 +172.31.41.98 +172.31.43.121 +172.31.37.120 +172.31.43.119 +172.31.42.116 +172.31.37.116 +172.31.47.116 +172.31.34.115 +172.31.37.111 +172.31.47.231 +172.31.45.230 +172.31.39.230 +172.31.45.229 +172.31.40.228 +172.31.46.227 +172.31.38.227 +172.31.46.71 +172.31.45.71 +172.31.34.70 +172.31.40.68 +172.31.47.127 +172.31.32.127 +172.31.45.126 +172.31.47.125 +172.31.38.94 +172.31.39.90 +172.31.41.85 +172.31.47.85 +172.31.39.81 +172.31.42.76 +172.31.47.76 +172.31.32.75 +172.31.40.35 +172.31.46.35 +172.31.47.35 +172.31.40.34 +172.31.38.34 +172.31.45.33 +172.31.47.33 +172.31.39.95 +172.31.43.47 +172.31.40.47 +172.31.45.46 +172.31.34.43 +172.31.46.41 +172.31.38.41 +172.31.39.37 +172.31.38.36 +172.31.45.5 +172.31.38.3 +172.31.36.63 +172.31.44.53 +172.31.47.52 +172.31.35.49 +172.31.40.49 +172.31.34.49 +172.31.47.17 +172.31.46.15 +172.31.37.13 +172.31.38.13 +172.31.46.10 +172.31.35.9 +172.31.33.9 +172.31.38.7 +172.31.35.31 +172.31.34.30 +172.31.43.29 +172.31.47.28 +172.31.45.25 +172.31.44.22 +172.31.33.21 +172.31.47.19 +172.31.37.31 + +) + +key=~/.ssh/junchao.pem +client_num=128 diff --git a/scripts/deploy/config/kv_performance_server_128_small.conf b/scripts/deploy/config/kv_performance_server_128_small.conf new file mode 100644 index 000000000..05efa7071 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_128_small.conf @@ -0,0 +1,261 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +172.31.21.137 +172.31.16.140 +172.31.26.139 +172.31.21.141 +172.31.27.140 +172.31.24.142 +172.31.31.142 +172.31.31.146 +172.31.19.145 +172.31.23.149 +172.31.30.149 +172.31.22.218 +172.31.17.214 +172.31.30.221 +172.31.24.220 +172.31.29.162 +172.31.24.221 +172.31.19.167 +172.31.21.166 +172.31.27.178 +172.31.18.177 +172.31.17.183 +172.31.21.179 +172.31.26.129 +172.31.16.190 +172.31.27.133 +172.31.29.133 +172.31.19.250 +172.31.27.250 +172.31.16.251 +172.31.26.250 +172.31.29.254 +172.31.21.252 +172.31.25.199 +172.31.23.195 +172.31.25.200 +172.31.28.199 +172.31.19.203 +172.31.28.201 +172.31.23.204 +172.31.26.203 +172.31.18.213 +172.31.19.208 +172.31.27.27 +172.31.17.24 +172.31.21.30 +172.31.25.29 +172.31.29.31 +172.31.21.54 +172.31.22.53 +172.31.27.60 +172.31.23.56 +172.31.25.63 +172.31.30.61 +172.31.27.2 +172.31.28.0 +172.31.26.4 +172.31.31.3 +172.31.20.8 +172.31.24.6 +172.31.23.21 +172.31.26.11 +172.31.29.23 +172.31.31.21 +172.31.25.82 +172.31.20.81 +172.31.25.87 +172.31.27.85 +172.31.26.93 +172.31.16.90 +172.31.24.94 +172.31.26.94 +172.31.19.95 +172.31.17.94 +172.31.30.44 +172.31.21.33 +172.31.26.47 +172.31.27.47 +172.31.26.50 +172.31.22.49 +172.31.19.122 +172.31.31.121 +172.31.24.127 +172.31.24.126 +172.31.21.65 +172.31.26.65 +172.31.29.70 +172.31.22.66 +172.31.19.72 +172.31.26.70 +172.31.22.74 +172.31.23.72 +172.31.27.76 +172.31.24.75 +172.31.19.81 +172.31.16.81 +172.31.35.29 +172.31.39.3 +172.31.37.1 +172.31.38.59 +172.31.41.56 +172.31.47.53 +172.31.46.47 +172.31.36.45 +172.31.44.45 +172.31.43.21 +172.31.40.17 +172.31.39.16 +172.31.34.14 +172.31.44.12 +172.31.44.8 +172.31.34.6 +172.31.33.5 +172.31.37.85 +172.31.42.84 +172.31.36.83 +172.31.47.82 +172.31.32.82 +172.31.46.80 +172.31.34.79 +172.31.32.79 +172.31.35.44 +172.31.42.39 +172.31.35.39 +172.31.34.37 +172.31.35.36 +172.31.41.32 +172.31.44.91 +172.31.43.89 +172.31.46.126 +172.31.41.120 +172.31.47.120 +172.31.41.118 +172.31.40.118 +172.31.43.116 +172.31.34.116 +172.31.47.111 +172.31.47.78 +172.31.44.75 +172.31.34.75 +172.31.35.72 +172.31.33.71 +172.31.41.71 +172.31.35.68 +172.31.47.64 +172.31.40.98 +172.31.36.158 +172.31.35.157 +172.31.33.156 +172.31.36.154 +172.31.32.153 +172.31.35.153 +172.31.45.153 +172.31.33.110 +172.31.47.108 +172.31.45.105 +172.31.45.104 +172.31.41.104 +172.31.41.101 +172.31.40.99 +172.31.43.98 +172.31.40.138 +172.31.46.137 +172.31.45.137 +172.31.43.132 +172.31.39.131 +172.31.33.131 +172.31.46.130 +172.31.34.129 +172.31.39.153 +172.31.37.152 +172.31.34.151 +172.31.43.148 +172.31.45.141 +172.31.39.140 +172.31.41.139 +172.31.35.139 +172.31.39.178 +172.31.39.177 +172.31.47.176 +172.31.37.176 +172.31.43.175 +172.31.33.173 +172.31.46.170 +172.31.33.169 +172.31.38.190 +172.31.35.188 +172.31.44.187 +172.31.38.185 +172.31.35.183 +172.31.39.182 +172.31.36.182 +172.31.43.179 +172.31.40.220 +172.31.37.219 +172.31.32.215 +172.31.46.215 +172.31.40.215 +172.31.42.214 +172.31.41.212 +172.31.39.207 +172.31.37.166 +172.31.34.163 +172.31.37.162 +172.31.43.162 +172.31.36.161 +172.31.39.160 +172.31.36.223 +172.31.44.221 +172.31.32.248 +172.31.41.246 +172.31.35.246 +172.31.44.245 +172.31.44.243 +172.31.43.235 +172.31.44.226 +172.31.42.206 +172.31.33.204 +172.31.45.199 +172.31.44.196 +172.31.35.194 +172.31.39.193 +172.31.36.254 +172.31.40.252 +) + +key=~/.ssh/junchao.pem +client_num=128 diff --git a/scripts/deploy/config/kv_performance_server_16.conf b/scripts/deploy/config/kv_performance_server_16.conf new file mode 100644 index 000000000..cc3def695 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_16.conf @@ -0,0 +1,37 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +) + +key=~/.ssh/junchao.pem +client_num=16 diff --git a/scripts/deploy/config/kv_performance_server_16_2.conf b/scripts/deploy/config/kv_performance_server_16_2.conf new file mode 100644 index 000000000..e77ce5698 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_16_2.conf @@ -0,0 +1,53 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +172.31.21.137 +172.31.16.140 +172.31.26.139 +172.31.21.141 +172.31.27.140 +172.31.24.142 +172.31.31.142 +172.31.31.146 +172.31.19.145 +172.31.23.149 +172.31.30.149 +172.31.22.218 +172.31.17.214 +172.31.30.221 +172.31.24.220 +172.31.29.162 +) + +key=~/.ssh/junchao.pem +client_num=32 diff --git a/scripts/deploy/config/kv_performance_server_32.conf b/scripts/deploy/config/kv_performance_server_32.conf new file mode 100644 index 000000000..7a428de10 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_32.conf @@ -0,0 +1,69 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +172.31.21.137 +172.31.16.140 +172.31.26.139 +172.31.21.141 +172.31.27.140 +172.31.24.142 +172.31.31.142 +172.31.31.146 +172.31.19.145 +172.31.23.149 +172.31.30.149 +172.31.22.218 +172.31.17.214 +172.31.30.221 +172.31.24.220 +172.31.29.162 +172.31.24.221 +172.31.19.167 +172.31.21.166 +172.31.27.178 +172.31.18.177 +172.31.17.183 +172.31.21.179 +172.31.26.129 +172.31.16.190 +172.31.27.133 +172.31.29.133 +172.31.19.250 +172.31.27.250 +172.31.16.251 +172.31.26.250 +172.31.29.254 +) + +key=~/.ssh/junchao.pem +client_num=32 diff --git a/scripts/deploy/config/kv_performance_server_64.conf b/scripts/deploy/config/kv_performance_server_64.conf new file mode 100644 index 000000000..062b1c0e3 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_64.conf @@ -0,0 +1,133 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +172.31.19.156 +172.31.22.106 +172.31.24.98 +172.31.23.110 +172.31.27.109 +172.31.31.111 +172.31.28.110 +172.31.20.117 +172.31.30.116 +172.31.16.120 +172.31.28.119 +172.31.27.136 +172.31.20.133 +172.31.22.136 +172.31.26.136 +172.31.30.139 +172.31.21.137 +172.31.16.140 +172.31.26.139 +172.31.21.141 +172.31.27.140 +172.31.24.142 +172.31.31.142 +172.31.31.146 +172.31.19.145 +172.31.23.149 +172.31.30.149 +172.31.22.218 +172.31.17.214 +172.31.30.221 +172.31.24.220 +172.31.29.162 +172.31.24.221 +172.31.19.167 +172.31.21.166 +172.31.27.178 +172.31.18.177 +172.31.17.183 +172.31.21.179 +172.31.26.129 +172.31.16.190 +172.31.27.133 +172.31.29.133 +172.31.19.250 +172.31.27.250 +172.31.16.251 +172.31.26.250 +172.31.29.254 +172.31.21.252 +172.31.25.199 +172.31.23.195 +172.31.25.200 +172.31.28.199 +172.31.19.203 +172.31.28.201 +172.31.23.204 +172.31.26.203 +172.31.18.213 +172.31.19.208 +172.31.27.27 +172.31.17.24 +172.31.21.30 +172.31.25.29 +172.31.29.31 +172.31.21.54 +172.31.22.53 +172.31.27.60 +172.31.23.56 +172.31.25.63 +172.31.30.61 +172.31.27.2 +172.31.28.0 +172.31.26.4 +172.31.31.3 +172.31.20.8 +172.31.24.6 +172.31.23.21 +172.31.26.11 +172.31.29.23 +172.31.31.21 +172.31.25.82 +172.31.20.81 +172.31.25.87 +172.31.27.85 +172.31.26.93 +172.31.16.90 +172.31.24.94 +172.31.26.94 +172.31.19.95 +172.31.17.94 +172.31.30.44 +172.31.21.33 +172.31.26.47 +172.31.27.47 +172.31.26.50 +172.31.22.49 +172.31.19.122 +172.31.31.121 +172.31.24.127 +172.31.24.126 +172.31.21.65 +172.31.26.65 +172.31.29.70 +172.31.22.66 +172.31.19.72 +172.31.26.70 +172.31.22.74 +172.31.23.72 +172.31.27.76 +172.31.24.75 +172.31.19.81 +172.31.16.81 +) + +key=~/.ssh/junchao.pem +client_num=64 diff --git a/scripts/deploy/config/kv_performance_server_8.conf b/scripts/deploy/config/kv_performance_server_8.conf new file mode 100644 index 000000000..a97d27ce2 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_8.conf @@ -0,0 +1,21 @@ +iplist=( +172.31.25.224 +172.31.20.228 +172.31.18.224 +172.31.16.230 +172.31.31.229 +172.31.26.233 +172.31.29.231 +172.31.17.244 +172.31.17.236 +172.31.26.249 +172.31.18.247 +172.31.16.151 +172.31.31.151 +172.31.31.155 +172.31.17.155 +172.31.22.98 +) + +key=~/.ssh/junchao.pem +client_num=8 diff --git a/scripts/deploy/config/kv_performance_server_small_128.conf b/scripts/deploy/config/kv_performance_server_small_128.conf new file mode 100644 index 000000000..af9522d3b --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_small_128.conf @@ -0,0 +1,261 @@ +iplist=( +172.31.41.159 +172.31.37.96 +172.31.45.102 +172.31.41.102 +172.31.39.152 +172.31.43.155 +172.31.45.156 +172.31.45.157 +172.31.37.117 +172.31.45.118 +172.31.37.119 +172.31.39.120 +172.31.37.103 +172.31.45.103 +172.31.47.105 +172.31.37.115 +172.31.45.124 +172.31.33.126 +172.31.35.64 +172.31.37.65 +172.31.39.121 +172.31.37.121 +172.31.33.123 +172.31.45.123 +172.31.35.81 +172.31.37.83 +172.31.39.88 +172.31.43.89 +172.31.33.65 +172.31.47.70 +172.31.45.70 +172.31.39.77 +172.31.39.33 +172.31.35.34 +172.31.37.35 +172.31.33.40 +172.31.35.90 +172.31.41.91 +172.31.43.93 +172.31.41.93 +172.31.37.46 +172.31.45.50 +172.31.39.54 +172.31.47.58 +172.31.35.41 +172.31.45.42 +172.31.39.42 +172.31.41.44 +172.31.47.0 +172.31.39.0 +172.31.43.3 +172.31.37.12 +172.31.41.60 +172.31.47.60 +172.31.45.62 +172.31.33.0 +172.31.37.20 +172.31.47.24 +172.31.35.30 +172.31.43.30 +172.31.47.14 +172.31.45.15 +172.31.37.17 +172.31.45.19 +172.31.32.227 +172.31.40.229 +172.31.44.232 +172.31.44.234 +172.31.39.30 +172.31.43.31 +172.31.32.225 +172.31.38.225 +172.31.32.243 +172.31.34.243 +172.31.32.247 +172.31.40.252 +172.31.46.234 +172.31.40.235 +172.31.34.235 +172.31.32.242 +172.31.32.196 +172.31.32.201 +172.31.36.204 +172.31.36.208 +172.31.34.253 +172.31.34.254 +172.31.44.254 +172.31.40.193 +172.31.38.165 +172.31.36.172 +172.31.40.172 +172.31.32.173 +172.31.40.210 +172.31.34.212 +172.31.38.215 +172.31.46.216 +172.31.40.175 +172.31.38.176 +172.31.38.178 +172.31.34.128 +172.31.44.173 +172.31.38.173 +172.31.44.174 +172.31.42.174 +172.31.38.136 +172.31.36.141 +172.31.46.141 +172.31.42.147 +172.31.34.130 +172.31.40.130 +172.31.46.132 +172.31.32.135 +172.31.38.151 +172.31.32.151 +172.31.44.152 +172.31.40.153 +172.31.40.147 +172.31.42.149 +172.31.46.149 +172.31.32.150 +172.31.46.96 +172.31.34.98 +172.31.44.100 +172.31.46.102 +172.31.40.154 +172.31.38.156 +172.31.38.158 +172.31.36.159 +172.31.34.113 +172.31.46.113 +172.31.38.118 +172.31.40.119 +172.31.40.104 +172.31.42.105 +172.31.34.106 +172.31.36.106 +172.31.36.125 +172.31.46.69 +172.31.42.70 +172.31.44.72 +172.31.44.123 +172.31.40.124 +172.31.38.124 +172.31.36.124 +172.31.36.76 +172.31.40.77 +172.31.46.78 +172.31.44.78 +172.31.32.72 +172.31.44.73 +172.31.32.73 +172.31.32.74 +172.31.44.85 +172.31.38.86 +172.31.46.87 +172.31.36.88 +172.31.36.80 +172.31.36.82 +172.31.42.83 +172.31.32.84 +172.31.44.92 +172.31.34.92 +172.31.42.94 +172.31.32.95 +172.31.46.89 +172.31.34.90 +172.31.46.91 +172.31.34.91 +172.31.46.39 +172.31.40.39 +172.31.42.41 +172.31.44.46 +172.31.42.34 +172.31.38.35 +172.31.42.36 +172.31.38.39 +172.31.32.63 +172.31.46.0 +172.31.40.2 +172.31.38.2 +172.31.46.54 +172.31.32.55 +172.31.40.56 +172.31.34.57 +172.31.34.7 +172.31.42.12 +172.31.36.12 +172.31.34.16 +172.31.36.3 +172.31.42.3 +172.31.42.5 +172.31.38.5 +172.31.36.26 +172.31.42.27 +172.31.38.27 +172.31.42.28 +172.31.44.19 +172.31.38.20 +172.31.34.21 +172.31.32.23 +172.31.43.233 +172.31.47.235 +172.31.37.235 +172.31.39.236 +172.31.41.226 +172.31.47.229 +172.31.41.229 +172.31.47.230 +172.31.35.245 +172.31.33.246 +172.31.45.248 +172.31.41.252 +172.31.33.236 +172.31.45.238 +172.31.35.242 +172.31.47.242 +172.31.45.192 +172.31.47.199 +172.31.45.202 +172.31.35.203 +172.31.47.253 +172.31.45.253 +172.31.37.253 +172.31.35.255 +172.31.45.208 +172.31.43.218 +172.31.45.218 +172.31.47.222 +172.31.47.203 +172.31.45.204 +172.31.41.207 +172.31.47.207 +172.31.33.165 +172.31.43.171 +172.31.37.173 +172.31.39.174 +172.31.33.160 +172.31.33.162 +172.31.45.163 +172.31.37.164 +172.31.45.183 +172.31.41.184 +172.31.41.188 +172.31.43.131 +172.31.41.175 +172.31.45.178 +172.31.33.180 +172.31.33.181 +172.31.39.135 +172.31.47.135 +172.31.37.138 +172.31.45.149 +172.31.39.132 +172.31.35.132 +172.31.33.133 +172.31.41.134 +) + +key=~/.ssh/junchao.pem +client_num=128 diff --git a/scripts/deploy/config/kv_performance_server_small_16.conf b/scripts/deploy/config/kv_performance_server_small_16.conf new file mode 100644 index 000000000..ec34db0ad --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_small_16.conf @@ -0,0 +1,37 @@ +iplist=( +172.31.41.159 +172.31.37.96 +172.31.45.102 +172.31.41.102 +172.31.39.152 +172.31.43.155 +172.31.45.156 +172.31.45.157 +172.31.37.117 +172.31.45.118 +172.31.37.119 +172.31.39.120 +172.31.37.103 +172.31.45.103 +172.31.47.105 +172.31.37.115 +172.31.45.124 +172.31.33.126 +172.31.35.64 +172.31.37.65 +172.31.39.121 +172.31.37.121 +172.31.33.123 +172.31.45.123 +172.31.35.81 +172.31.37.83 +172.31.39.88 +172.31.43.89 +172.31.33.65 +172.31.47.70 +172.31.45.70 +172.31.39.77 +) + +key=~/.ssh/junchao.pem +client_num=16 diff --git a/scripts/deploy/config/kv_performance_server_small_32.conf b/scripts/deploy/config/kv_performance_server_small_32.conf new file mode 100644 index 000000000..88424ea58 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_small_32.conf @@ -0,0 +1,69 @@ +iplist=( +172.31.41.159 +172.31.37.96 +172.31.45.102 +172.31.41.102 +172.31.39.152 +172.31.43.155 +172.31.45.156 +172.31.45.157 +172.31.37.117 +172.31.45.118 +172.31.37.119 +172.31.39.120 +172.31.37.103 +172.31.45.103 +172.31.47.105 +172.31.37.115 +172.31.45.124 +172.31.33.126 +172.31.35.64 +172.31.37.65 +172.31.39.121 +172.31.37.121 +172.31.33.123 +172.31.45.123 +172.31.35.81 +172.31.37.83 +172.31.39.88 +172.31.43.89 +172.31.33.65 +172.31.47.70 +172.31.45.70 +172.31.39.77 +172.31.39.33 +172.31.35.34 +172.31.37.35 +172.31.33.40 +172.31.35.90 +172.31.41.91 +172.31.43.93 +172.31.41.93 +172.31.37.46 +172.31.45.50 +172.31.39.54 +172.31.47.58 +172.31.35.41 +172.31.45.42 +172.31.39.42 +172.31.41.44 +172.31.47.0 +172.31.39.0 +172.31.43.3 +172.31.37.12 +172.31.41.60 +172.31.47.60 +172.31.45.62 +172.31.33.0 +172.31.37.20 +172.31.47.24 +172.31.35.30 +172.31.43.30 +172.31.47.14 +172.31.45.15 +172.31.37.17 +172.31.45.19 +) + +key=~/.ssh/junchao.pem +client_num=32 diff --git a/scripts/deploy/config/kv_performance_server_small_64.conf b/scripts/deploy/config/kv_performance_server_small_64.conf new file mode 100644 index 000000000..0a176d2ee --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_small_64.conf @@ -0,0 +1,133 @@ +iplist=( +172.31.41.159 +172.31.37.96 +172.31.45.102 +172.31.41.102 +172.31.39.152 +172.31.43.155 +172.31.45.156 +172.31.45.157 +172.31.37.117 +172.31.45.118 +172.31.37.119 +172.31.39.120 +172.31.37.103 +172.31.45.103 +172.31.47.105 +172.31.37.115 +172.31.45.124 +172.31.33.126 +172.31.35.64 +172.31.37.65 +172.31.39.121 +172.31.37.121 +172.31.33.123 +172.31.45.123 +172.31.35.81 +172.31.37.83 +172.31.39.88 +172.31.43.89 +172.31.33.65 +172.31.47.70 +172.31.45.70 +172.31.39.77 +172.31.39.33 +172.31.35.34 +172.31.37.35 +172.31.33.40 +172.31.35.90 +172.31.41.91 +172.31.43.93 +172.31.41.93 +172.31.37.46 +172.31.45.50 +172.31.39.54 +172.31.47.58 +172.31.35.41 +172.31.45.42 +172.31.39.42 +172.31.41.44 +172.31.47.0 +172.31.39.0 +172.31.43.3 +172.31.37.12 +172.31.41.60 +172.31.47.60 +172.31.45.62 +172.31.33.0 +172.31.37.20 +172.31.47.24 +172.31.35.30 +172.31.43.30 +172.31.47.14 +172.31.45.15 +172.31.37.17 +172.31.45.19 +172.31.32.227 +172.31.40.229 +172.31.44.232 +172.31.44.234 +172.31.39.30 +172.31.43.31 +172.31.32.225 +172.31.38.225 +172.31.32.243 +172.31.34.243 +172.31.32.247 +172.31.40.252 +172.31.46.234 +172.31.40.235 +172.31.34.235 +172.31.32.242 +172.31.32.196 +172.31.32.201 +172.31.36.204 +172.31.36.208 +172.31.34.253 +172.31.34.254 +172.31.44.254 +172.31.40.193 +172.31.38.165 +172.31.36.172 +172.31.40.172 +172.31.32.173 +172.31.40.210 +172.31.34.212 +172.31.38.215 +172.31.46.216 +172.31.40.175 +172.31.38.176 +172.31.38.178 +172.31.34.128 +172.31.44.173 +172.31.38.173 +172.31.44.174 +172.31.42.174 +172.31.38.136 +172.31.36.141 +172.31.46.141 +172.31.42.147 +172.31.34.130 +172.31.40.130 +172.31.46.132 +172.31.32.135 +172.31.38.151 +172.31.32.151 +172.31.44.152 +172.31.40.153 +172.31.40.147 +172.31.42.149 +172.31.46.149 +172.31.32.150 +172.31.46.96 +172.31.34.98 +172.31.44.100 +172.31.46.102 +172.31.40.154 +172.31.38.156 +172.31.38.158 +172.31.36.159 +) + +key=~/.ssh/junchao.pem +client_num=64 diff --git a/scripts/deploy/config/kv_performance_server_small_8.conf b/scripts/deploy/config/kv_performance_server_small_8.conf new file mode 100644 index 000000000..3a807201a --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_small_8.conf @@ -0,0 +1,20 @@ +iplist=( +172.31.41.159 +172.31.37.96 +172.31.45.102 +172.31.41.102 +172.31.39.152 +172.31.43.155 +172.31.45.156 +172.31.45.157 +172.31.37.117 +172.31.45.118 +172.31.37.119 +172.31.39.120 +172.31.37.103 +172.31.45.103 +172.31.47.105 +172.31.37.115 +) + +client_num=8 diff --git a/scripts/deploy/config/kv_performance_server_test_5.conf b/scripts/deploy/config/kv_performance_server_test_5.conf new file mode 100644 index 000000000..f064b8305 --- /dev/null +++ b/scripts/deploy/config/kv_performance_server_test_5.conf @@ -0,0 +1,10 @@ +iplist=( +172.31.66.2 +172.31.72.164 +172.31.73.238 +172.31.77.118 +172.31.74.248 +) + +key=~/.ssh/junchao.pem +client_num=1 diff --git a/scripts/deploy/config/kv_server.conf b/scripts/deploy/config/kv_server.conf index 8b5358b2c..a811f7681 100644 --- a/scripts/deploy/config/kv_server.conf +++ b/scripts/deploy/config/kv_server.conf @@ -6,4 +6,3 @@ iplist=( 172.31.57.186 ) -key=~/.ssh/your_key diff --git a/scripts/deploy/config/poe.config b/scripts/deploy/config/poe.config new file mode 100644 index 000000000..c5092a94c --- /dev/null +++ b/scripts/deploy/config/poe.config @@ -0,0 +1,10 @@ +{ + "clientBatchNum": 100, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 32, + "worker_num": 2, + "input_worker_num": 1, + "output_worker_num": 10 +} diff --git a/scripts/deploy/config/rcc.config b/scripts/deploy/config/rcc.config new file mode 100644 index 000000000..eec20b8c8 --- /dev/null +++ b/scripts/deploy/config/rcc.config @@ -0,0 +1,10 @@ +{ + "clientBatchNum": 1000, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 32, + "worker_num": 30, + "input_worker_num": 1, + "output_worker_num": 10 +} diff --git a/scripts/deploy/config/template.config b/scripts/deploy/config/template.config new file mode 100644 index 000000000..9f66f5289 --- /dev/null +++ b/scripts/deploy/config/template.config @@ -0,0 +1,10 @@ +{ + "clientBatchNum": 800, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 512, + "worker_num": 16, + "input_worker_num": 5, + "output_worker_num": 5 +} diff --git a/scripts/deploy/config/tusk.config b/scripts/deploy/config/tusk.config new file mode 100644 index 000000000..f940544e7 --- /dev/null +++ b/scripts/deploy/config/tusk.config @@ -0,0 +1,11 @@ +{ + "clientBatchNum": 200, + "enable_viewchange": false, + "recovery_enabled": false, + "max_client_complaint_num":10, + "max_process_txn": 384, + "worker_num": 10, + "input_worker_num": 1, + "output_worker_num": 5, + "failure_num": 1 +} \ No newline at end of file diff --git a/scripts/deploy/manage_alicloud_instance.sh b/scripts/deploy/manage_alicloud_instance.sh new file mode 100755 index 000000000..857c1afd4 --- /dev/null +++ b/scripts/deploy/manage_alicloud_instance.sh @@ -0,0 +1,314 @@ +#!/bin/bash + +# Sample: ./manage_alicloud_instance.sh new wan 8 write run shutdown + +# function: distribute the total number +distribute_evenly() { + local total=$1 + local listsize=$2 + + local base_value=$((total / listsize)) + local remainder=$((total % listsize)) + + # 初始化数组并分配 + for ((i = 0; i < listsize; i++)); do + if ((i < remainder)); then + echo $((base_value + 1)) + else + echo $base_value + fi + done +} + +rearrange_ips() { + local ip_list=("$@") # 所有 IP 地址列表 + local amount_list=("${!#}") # 最后一个参数是以空格分隔的 amount_list + IFS=' ' read -r -a amount_list <<< "$amount_list" + + # 初始化子列表 + local sublists=() + local start=0 + for amount in "${amount_list[@]}"; do + sublists+=("$(IFS=','; echo "${ip_list[@]:$start:$amount}")") + start=$((start + amount)) + done + + # 获取最大 region 长度 + local max_amount=${amount_list[0]} + for amount in "${amount_list[@]}"; do + if [[ $amount -gt $max_amount ]]; then + max_amount=$amount + fi + done + + # 轮流排序 IP + local final_ips=() + for i in $(seq 0 $((max_amount - 1))); do + for j in "${!sublists[@]}"; do + local region_ips=(${sublists[$j]//,/ }) + if [[ $i -lt ${#region_ips[@]} ]]; then + final_ips+=("${region_ips[$i]}") + fi + done + done + + echo "${final_ips[@]}" # 返回排序后的 IP 列表 +} + +mode=$1 +shift +network=$1 +shift +amount=$1 +shift +listsize=0 +echo $mode, $network + +region_list=('eu-central-1' 'us-west-1' 'ap-southeast-1' 'me-east-1') +imageid_list=('m-gw8atjzmmv35esf7t0ol' 'm-rj9hvjdzwmn5hszxi7rl' 'm-t4n9apvlakv21suofn9c' 'm-eb32jb9t0l0fabyiuswx') +zoneid_list=('eu-central-1a' 'us-west-1a' 'ap-southeast-1a' 'me-east-1a') +instance_type_list=('ecs.e-c1m4.large' 'ecs.e-c1m4.large' 'ecs.e-c1m4.large' 'ecs.g6.large') +security_groupid_list=('sg-gw882198cp93dc3he2pp' 'sg-rj91tgsgh70v6v1y0sqw' 'sg-t4ngrbld64k3fls433bj' 'sg-eb32jb9t0l0f9u76i0n0') +vpc_list=('vpc-gw87tw2ohh8amcdkvoeci' 'vpc-rj9jfm8myo5eug7gbbi31' 'vpc-t4nskgim87ibkhz04y4up' 'vpc-eb3nl8lrg1jh792ahzzny') +vswich_list=('vsw-gw818gr9injn3xq6wqvhv' 'vsw-rj9uoqiwyrorgxnrfr7tc' 'vsw-t4ny2ave4421x41cicpah' 'vsw-eb3ge3h2j2s0rsqwaigcy') +disk_cata_list=('cloud_auto' 'cloud_auto' 'cloud_auto' 'cloud_essd') + +listsize=${#region_list[@]} + +amount_list=($(distribute_evenly $amount $listsize)) + +if [[ $mode == "new" ]]; then + if [[ $network == "lan" ]]; then + region='cn-hongkong' + imageid='m-j6c7k48y8pips9b3gark' + zoneid='cn-hongkong-b' + instance_type='ecs.g7t.large' + security_groupid='sg-j6c983lbmrcoilavm6vf' + vswichid='vsw-j6ctft5mb2zny49xzub9j' + + aliyun ecs RunInstances \ + --RegionId $region \ + --ImageId $imageid \ + --ZoneId $zoneid \ + --InstanceType $instance_type \ + --SecurityGroupId $security_groupid \ + --VSwitchId $vswichid \ + --InstanceName 'shaokang-fides-instance' \ + --InternetMaxBandwidthIn 100 \ + --InternetMaxBandwidthOut 100 \ + --HostName 'fides-instance' \ + --UniqueSuffix true \ + --InternetChargeType 'PayByTraffic' \ + --SystemDisk.Size 80 \ + --SystemDisk.Category cloud_auto \ + --Tag.1.Key name \ + --Tag.1.Value 'fides-instance' \ + --Amount $amount + + echo "sleep 60" + sleep 60 + + elif [[ $network == "wan" ]]; then + + + # 遍历每个区域的配置并运行实例 + for ((i = 0; i < listsize; i++)); do + region=${region_list[i]} + imageid=${imageid_list[i]} + zoneid=${zoneid_list[i]} + instance_type=${instance_type_list[i]} + security_groupid=${security_groupid_list[i]} + vswichid=${vswich_list[i]} + amount=${amount_list[i]} + disk_cata=${disk_cata_list[i]} + + echo "$region, $imageid, $zoneid, $instance_type, $security_groupid, $vswichid, $amount" + + aliyun ecs RunInstances \ + --RegionId "$region" \ + --ImageId "$imageid" \ + --ZoneId "$zoneid" \ + --InstanceType "$instance_type" \ + --SecurityGroupId "$security_groupid" \ + --VSwitchId "$vswichid" \ + --InstanceName 'shaokang-fides-instance' \ + --InternetMaxBandwidthIn 100 \ + --InternetMaxBandwidthOut 100 \ + --HostName 'fides-instance' \ + --UniqueSuffix true \ + --InternetChargeType 'PayByTraffic' \ + --SystemDisk.Size 80 \ + --SystemDisk.Category "$disk_cata" \ + --Tag.1.Key name \ + --Tag.1.Value 'fides-instance' \ + --Amount $amount + + # wait for some time + sleep 0.5 + done + + echo "sleep 60" + sleep 60 + fi +fi + + +# Describe Instances +instance_ids=() +public_ips=() +private_ips=() + +need_describe=false + +for arg in "$@"; do + if [[ "$arg" == "shutdown" || "$arg" == "write" ]]; then + need_describe=true + break + fi +done + +if [[ "$need_describe" == true ]]; then + echo Need Describe. + + if [[ $network == "lan" ]]; then + + data=$(aliyun ecs DescribeInstances \ + --region cn-hongkong \ + --Tag.1.Key name \ + --Tag.1.Value fides-instance \ + --MaxResults 100 \ + --output cols=InstanceId,PublicIpAddress,VpcAttributes.PrivateIpAddress rows=Instances.Instance[]) + # --output cols=InstanceId,InstanceName,PublicIpAddress,VpcAttributes.PrivateIpAddress rows=Instances.Instance[]) + # echo $info + + # Sample return data + # data="InstanceId | PublicIpAddress | VpcAttributes.PrivateIpAddress ---------- | --------------- | ------------------------------ i-j6c48395f9rus4psiw7y | map[IpAddress:[47.242.8.123]] | map[IpAddress:[172.18.218.174]] i-j6c48395f9rus4psiw7z | map[IpAddress:[47.242.220.33]] | map[IpAddress:[172.18.218.173]] i-j6c48395f9rus4psiw7x | map[IpAddress:[47.242.222.206]] | map[IpAddress:[172.18.218.175]]" + + # Replace 'i-' into '\n' + formatted_data=$(echo "$data" | sed 's/i-/\ni-/g') + + # Iterate each row. + while read -r line; do + instance_id=$(echo "$line" | awk -F '|' '{print $1}' | xargs) + instance_ids+=("$instance_id") + public_ip=$(echo "$line" | awk -F '|' '{print $2}' | sed 's/.*IpAddress:\[\(.*\)\].*/\1/' | sed 's/]//g' | xargs) + public_ips+=("$public_ip") + private_ip=$(echo "$line" | awk -F '|' '{print $3}' | sed 's/.*IpAddress:\[\(.*\)\].*/\1/' | sed 's/]//g' | xargs) + private_ips+=("$private_ip") + # echo "$instance_id, $public_ip, $private_ip" + done <<< "$(echo "$formatted_data" | grep 'i-')" + + echo "Instance IDs: ${instance_ids[@]}" + echo "Public IPs: ${public_ips[@]}" + echo "Private IPs: ${private_ips[@]}" + + elif [[ $network == "wan" ]]; then + for region in "${region_list[@]}"; do + data=$(aliyun ecs DescribeInstances \ + --region $region \ + --Tag.1.Key name \ + --Tag.1.Value fides-instance \ + --MaxResults 100 \ + --output cols=InstanceId,PublicIpAddress,VpcAttributes.PrivateIpAddress rows=Instances.Instance[]) + echo $data + # Replace 'i-' into '\n' + formatted_data=$(echo "$data" | sed 's/i-/\ni-/g') + + # Iterate each row. + while read -r line; do + instance_id=$(echo "$line" | awk -F '|' '{print $1}' | xargs) + instance_ids+=("$instance_id") + public_ip=$(echo "$line" | awk -F '|' '{print $2}' | sed 's/.*IpAddress:\[\(.*\)\].*/\1/' | sed 's/]//g' | xargs) + public_ips+=("$public_ip") + private_ip=$(echo "$line" | awk -F '|' '{print $3}' | sed 's/.*IpAddress:\[\(.*\)\].*/\1/' | sed 's/]//g' | xargs) + private_ips+=("$private_ip") + done <<< "$(echo "$formatted_data" | grep 'i-')" + done + + echo "Instance IDs: ${instance_ids[@]}" + echo "Public IPs: ${public_ips[@]}" + echo "Private IPs: ${private_ips[@]}" + fi + +fi + +# public_ips=(11 12 13 21 22 23 31 32 41 42) + +if [[ $1 == "write" ]]; then + shift + # 定义文件名 + output_file="config/kv_performance_server.conf" + client_num=$(( ${#instance_ids[@]} / 2 )) + # 写入到文件中 + final_ips=() + if [[ $network == "wan" ]]; then + final_ips=($(rearrange_ips "${public_ips[@]}" "${amount_list[*]}")) + elif [[ $network == "lan" ]]; then + final_ips=("${private_ips[@]}") + fi + { + echo "iplist=(" + for ip in "${final_ips[@]}"; do + echo "$ip" + done + echo ")" + echo "" + echo "client_num=$client_num" + } > "$output_file" + + # 显示生成的文件内容 + cat "$output_file" +fi + +if [[ $1 == "run" ]]; then + shift + if [[ $network == "lan" ]]; then + # bash ./start_evaluation.sh simulate + bash ./start_evaluation.sh sgx + elif [[ $network == "wan" ]]; then + bash ./start_evaluation.sh simulate + fi +fi + + + +if [[ $1 == "shutdown" ]]; then + # Form a delete command containing all the instance_ids. + # Sample: + # aliyun ecs DeleteInstances \ + # --region cn-hongkong \ + # --InstanceId.1 i-1 \ + # --InstanceId.2 i-2 \ + # --Force true + + if [[ $network == "lan" ]]; then + counter=1 + delete_command="aliyun ecs DeleteInstances --region cn-hongkong" + for instance_id in "${instance_ids[@]}"; do + delete_command="$delete_command --InstanceId.$counter $instance_id" + ((counter++)) + done + delete_command="$delete_command --Force true" + echo $delete_command + $delete_command + elif [[ $network == "wan" ]]; then + echo shutdown in wan + instance_index=0 + for ((i = 0; i < listsize; i++)); do + if [[ ${amount_list[$i]} -gt 0 ]]; then + counter=1 + delete_command="aliyun ecs DeleteInstances --region ${region_list[$i]}" + # for instance_id in "${instance_ids[@]}"; do + + for ((j = 0; j < ${amount_list[$i]}; j++)); do + delete_command="$delete_command --InstanceId.$counter ${instance_ids[$instance_index]}" + ((counter++)) + ((instance_index++)) + done + delete_command="$delete_command --Force true" + echo $delete_command + $delete_command + fi + done + fi +fi \ No newline at end of file diff --git a/scripts/deploy/performance/calculate_result.py b/scripts/deploy/performance/calculate_result.py index 5fa595980..04f418d90 100644 --- a/scripts/deploy/performance/calculate_result.py +++ b/scripts/deploy/performance/calculate_result.py @@ -1,6 +1,23 @@ -import sys +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +import sys +total = 0 def read_tps(file): tps = [] lat = [] @@ -8,36 +25,127 @@ def read_tps(file): for l in f.readlines(): s = l.split() for r in s: - if(r.split(':')[0] == 'txn'): - tps.append(int(r.split(':')[1])) + try: + if(r.split(':')[0] == 'txn'): + tps.append(int(r.split(':')[1])) + except: + print("s:",s) if l.find("client latency") > 0: + print("get lat:",s) lat.append(float(s[-1].split(':')[-1])) return tps, lat -def cal_tps(tps): +def cal_tps(tps, tot): tps_sum = [] tps_max = 0 for v in tps: if v == 0: continue + if v < 1000: + continue tps_max = max(tps_max, v) tps_sum.append(v) + tps_sum.sort() + # tps_sum = tps_sum[1:-1] + # tps_sum = tps_sum[tot:-tot] + print("tsp:",tps_sum) print("max throughput:",tps_max) print("average throughput:",sum(tps_sum)/len(tps_sum)) + return tps_max, sum(tps_sum)/len(tps_sum) def cal_lat(lat): lat_sum = [] lat_max = 0 for v in lat: - if v == 0: + if v <= 0: continue lat_max = max(lat_max, v) lat_sum.append(v) print("max latency:",lat_max) print("average latency:",sum(lat_sum)/len(lat_sum)) + return lat_max, sum(lat_sum)/len(lat_sum) + +def read_breakdown(file): + queueing=[] # queuing latency + receive_proposal=[] # execute_prepare latency + verify_proposal=[] # verify latency + + commit=[] # commit latency + + commit_runtime=[] # commit_running + execute_queueing=[] # execute_queuing + execute=[] # execute latency + + commit_round=[] # how many round needed for commit. commit_round latency + + with open(file) as f: + for line in f: + if "queuing latency" in line: + lat=parse_latency(line, "queuing latency") + if (lat != float('nan') and lat>0.0): + queueing.append(lat) + if "execute_prepare latency" in line: + lat=parse_latency(line, "execute_prepare latency") + if (lat != float('nan') and lat>0.0): + receive_proposal.append(lat) + if "verify latency" in line: + lat=parse_latency(line, "verify latency") + if (lat != float('nan') and lat>0.0): + verify_proposal.append(lat) + if "commit latency" in line: + lat=parse_latency(line, "commit latency") + if (lat != float('nan') and lat>0.0): + commit.append(lat) + if "commit_running latency" in line: + lat=parse_latency(line, "commit_running latency") + if (lat != float('nan') and lat>0.0): + commit_runtime.append(lat) + if "execute_queuing latency" in line: + lat=parse_latency(line, "execute_queuing latency") + if (lat != float('nan') and lat>0.0): + execute_queueing.append(lat) + if "execute latency" in line: + lat=parse_latency(line, "execute latency") + if (lat != float('nan') and lat>0.0): + execute.append(lat) + if "commit_round latency" in line: + lat=parse_latency(line, "commit_round latency") + if (lat != float('nan') and lat>0.0): + commit_round.append(lat) + + return { + "queueing": queueing, + "receive_proposal": receive_proposal, + "verify_proposal": verify_proposal, + "commit": commit, + "commit_runtime": commit_runtime, + "execute_queueing": execute_queueing, + "execute": execute, + "commit_round": commit_round, + } + +def parse_latency(line, key): + """Extract latency value from a log line based on a key.""" + try: + return float(line.split(f"{key} :")[1].split()[0]) + except (IndexError, ValueError): + return None + +def analyze_breakdown(data): + """Analyze breakdown data: calculate average and max latencies.""" + for key, values in data.items(): + if values: + avg = sum(values) / len(values) + max_val = max(values) + min_val = min(values) + print(f"{key}: min = {min_val:.6f}, max = {max_val:.6f}, avg = {avg:.6f}") + else: + print(f"{key}: No data available.") + + if __name__ == '__main__': files = sys.argv[1:] @@ -46,10 +154,36 @@ def cal_lat(lat): tps = [] lat = [] + breakdown_data = { + "queueing": [], + "receive_proposal": [], + "verify_proposal": [], + "commit_runtime": [], + "commit": [], + "execute_queueing": [], + "execute": [], + "commit_round": [], + } + total = len(files) for f in files: t, l=read_tps(f) tps += t lat += l - cal_tps(tps) - cal_lat(lat) + # Read breakdown data + breakdown = read_breakdown(f) + for key in breakdown_data: + breakdown_data[key].extend(breakdown[key]) + + + # Analyze breakdown data + print("\nBreakdown Analysis:") + analyze_breakdown(breakdown_data) + + print() + max_tps, avg_tps = cal_tps(tps, len(files)) + max_lat, avg_lat = cal_lat(lat) + + print("max throughput:{} average throughput:{} " + "max latency:{} average latency:{} " + "replica num:{}".format(max_tps, avg_tps, max_lat, avg_lat, total)) diff --git a/scripts/deploy/performance/cassandra_performance.sh b/scripts/deploy/performance/cassandra_performance.sh new file mode 100755 index 000000000..a461f6dbb --- /dev/null +++ b/scripts/deploy/performance/cassandra_performance.sh @@ -0,0 +1,4 @@ +export server=//benchmark/protocols/cassandra:kv_server_performance +export TEMPLATE_PATH=$PWD/config/cassandra.config + +./performance/run_performance.sh $* diff --git a/scripts/deploy/performance/fair_performance.sh b/scripts/deploy/performance/fair_performance.sh new file mode 100755 index 000000000..fb0535efe --- /dev/null +++ b/scripts/deploy/performance/fair_performance.sh @@ -0,0 +1,4 @@ +export server=//benchmark/protocols/fairdag:kv_server_performance +export TEMPLATE_PATH=$PWD/config/fair.config + +./performance/run_performance.sh $* diff --git a/scripts/deploy/performance/fides_performance.sh b/scripts/deploy/performance/fides_performance.sh new file mode 100755 index 000000000..826c7fc81 --- /dev/null +++ b/scripts/deploy/performance/fides_performance.sh @@ -0,0 +1,5 @@ +export server=//benchmark/protocols/fides:kv_server_performance +export TEMPLATE_PATH=$PWD/config/fides.config + +./performance/run_performance.sh $* + diff --git a/scripts/deploy/performance/hs_performance.sh b/scripts/deploy/performance/hs_performance.sh new file mode 100755 index 000000000..94417439c --- /dev/null +++ b/scripts/deploy/performance/hs_performance.sh @@ -0,0 +1,49 @@ +export server=//benchmark/protocols/hs:kv_server_performance + +./script/deploy.sh $1 + +. ./script/load_config.sh $1 + +server_name=`echo "$server" | awk -F':' '{print $NF}'` +server_bin=${server_name} + +bazel run //benchmark/protocols/pbft:kv_service_tools + +for((i=1;;i++)) +do +config_file=$PWD/config_out/client${i}.config +if [ ! -f "$config_file" ]; then + break; +fi +echo "get cofigfile:"$config_file +/root/nexres/bazel-bin/benchmark/protocols/pbft/kv_service_tools $config_file +done + +sleep 60 + +echo "benchmark done" +count=1 +for ip in ${iplist[@]}; +do +`ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "killall -9 ${server_bin}"` +((count++)) +done + +while [ $count -gt 0 ]; do + wait $pids + count=`expr $count - 1` +done + + +echo "getting results" +for ip in ${iplist[@]}; +do + echo "scp -i ${key} root@${ip}:/root/${server_bin}.log ./${ip}_log" + `scp -i ${key} root@${ip}:/root/${server_bin}.log result_${ip}_log` +done + +python3 performance/calculate_result.py `ls result_*_log` > results.log + +rm -rf result_*_log +echo "save result to results.log" +cat results.log diff --git a/scripts/deploy/performance/ooohs_performance.sh b/scripts/deploy/performance/ooohs_performance.sh new file mode 100755 index 000000000..eb74144a1 --- /dev/null +++ b/scripts/deploy/performance/ooohs_performance.sh @@ -0,0 +1,50 @@ +export server=//benchmark/protocols/ooo_hs:kv_server_performance + +./script/deploy.sh $1 + +. ./script/load_config.sh $1 + +server_name=`echo "$server" | awk -F':' '{print $NF}'` +server_bin=${server_name} + + +bazel run //benchmark/protocols/pbft:kv_service_tools + +for((i=1;;i++)) +do +config_file=$PWD/config_out/client${i}.config +if [ ! -f "$config_file" ]; then + break; +fi +echo "get cofigfile:"$config_file +/root/nexres/bazel-bin/benchmark/protocols/pbft/kv_service_tools $config_file +done + +sleep 60 + +echo "benchmark done" +count=1 +for ip in ${iplist[@]}; +do +`ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "killall -9 ${server_bin}"` +((count++)) +done + +while [ $count -gt 0 ]; do + wait $pids + count=`expr $count - 1` +done + + +echo "getting results" +for ip in ${iplist[@]}; +do + echo "scp -i ${key} root@${ip}:/root/${server_bin}.log ./${ip}_log" + `scp -i ${key} root@${ip}:/root/${server_bin}.log result_${ip}_log` +done + +python3 performance/calculate_result.py `ls result_*_log` > results.log + +rm -rf result_*_log +echo "save result to results.log" +cat results.log diff --git a/scripts/deploy/performance/poe_performance.sh b/scripts/deploy/performance/poe_performance.sh new file mode 100755 index 000000000..3fd671290 --- /dev/null +++ b/scripts/deploy/performance/poe_performance.sh @@ -0,0 +1,6 @@ +export server=//benchmark/protocols/poe:kv_server_performance +export TEMPLATE_PATH=$PWD/config/poe.config +#export COPTS="--define enable_leveldb=True" +#export COPTS="-pg" + +./performance/run_performance.sh $* diff --git a/scripts/deploy/performance/rcc_performance.sh b/scripts/deploy/performance/rcc_performance.sh new file mode 100755 index 000000000..0602a8ef1 --- /dev/null +++ b/scripts/deploy/performance/rcc_performance.sh @@ -0,0 +1,6 @@ +export server=//benchmark/protocols/rcc:kv_server_performance +export TEMPLATE_PATH=$PWD/config/rcc.config +#export COPTS="--define enable_leveldb=True" +#export COPTS="-pg" + +./performance/run_performance.sh $* diff --git a/scripts/deploy/performance/run_performance.sh b/scripts/deploy/performance/run_performance.sh index 68bc9c26b..76749d466 100755 --- a/scripts/deploy/performance/run_performance.sh +++ b/scripts/deploy/performance/run_performance.sh @@ -1,4 +1,4 @@ -export server=//benchmark/protocols/pbft:kv_server_performance +#export COPTS="--define enable_leveldb=True" ./script/deploy.sh $1 @@ -7,30 +7,50 @@ export server=//benchmark/protocols/pbft:kv_server_performance server_name=`echo "$server" | awk -F':' '{print $NF}'` server_bin=${server_name} -bazel run //benchmark/protocols/pbft:kv_service_tools -- $PWD/config_out/client.config +bazel run //benchmark/protocols/pbft:kv_service_tools + +for((i=1;;i++)) +do +config_file=$PWD/config_out/client${i}.config +if [ ! -f "$config_file" ]; then + break; +fi +echo "get cofigfile:"$config_file +../../bazel-bin/benchmark/protocols/pbft/kv_service_tools $config_file +done sleep 60 +# for ip in ${iplist[@]}; +# do +# ssh $ssh_options_cloud -p 22 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "tc qdisc del dev eth0 root" & +# done +# wait + + echo "benchmark done" count=1 for ip in ${iplist[@]}; do -`ssh -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no ubuntu@${ip} "killall -9 ${server_bin}"` +`ssh $ssh_options_cloud -p 2222 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "killall -9 ${server_bin}"` & +# `ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "killall -9 ${server_bin}"` ((count++)) done +wait -while [ $count -gt 0 ]; do - wait $pids - count=`expr $count - 1` -done - +# while [ $count -gt 0 ]; do +# wait $pids +# count=`expr $count - 1` +# done echo "getting results" for ip in ${iplist[@]}; do - echo "scp -i ${key} ubuntu@${ip}:/home/ubuntu/${server_bin}.log ./${ip}_log" - `scp -i ${key} ubuntu@${ip}:/home/ubuntu/${server_bin}.log result_${ip}_log` + # echo "scp $ssh_options_cloud -i ${key} root@${ip}:/root/${server_bin}.log ./${ip}_log" + `scp $ssh_options_cloud -P 2222 -i ${key} root@${ip}:/root/${server_bin}.log result_${ip}_log` & + # `scp $ssh_options_cloud -i ${key} root@${ip}:/root/${server_bin}.log result_${ip}_log` done +wait python3 performance/calculate_result.py `ls result_*_log` > results.log diff --git a/scripts/deploy/poe_performance.sh b/scripts/deploy/poe_performance.sh new file mode 100755 index 000000000..0602a8ef1 --- /dev/null +++ b/scripts/deploy/poe_performance.sh @@ -0,0 +1,6 @@ +export server=//benchmark/protocols/rcc:kv_server_performance +export TEMPLATE_PATH=$PWD/config/rcc.config +#export COPTS="--define enable_leveldb=True" +#export COPTS="-pg" + +./performance/run_performance.sh $* diff --git a/scripts/deploy/script/deploy.sh b/scripts/deploy/script/deploy.sh index 87ab7dc92..12b7e7a58 100755 --- a/scripts/deploy/script/deploy.sh +++ b/scripts/deploy/script/deploy.sh @@ -8,9 +8,15 @@ set -e script_path=${BAZEL_WORKSPACE_PATH}/scripts -if [[ -z $server ]]; +echo "server is: $server" + +if [[ -z $server ]]; then + server=//service/kv:kv_service +fi + +if [[ -z $client_num ]]; then -server=//service/kv:kv_service +client_num=1 fi # obtain the src path @@ -18,7 +24,8 @@ server_path=`echo "$server" | sed 's/:/\//g'` server_path=${server_path:1} server_name=`echo "$server" | awk -F':' '{print $NF}'` server_bin=${server_name} -grafna_port=8090 +enclave_path=${BAZEL_WORKSPACE_PATH}/enclave/sgxcode/build/enclave/enclave.signed +#grafna_port=8090 bin_path=${BAZEL_WORKSPACE_PATH}/bazel-bin/${server_path} output_path=${script_path}/deploy/config_out @@ -39,6 +46,7 @@ echo "server name:"${server_bin} echo "admin config path:"${admin_key_path} echo "output path:"${output_path} echo "deploy to :"${deploy_iplist[@]} +echo "client num :"${client_num} # generate keys and certificates. @@ -46,10 +54,10 @@ cd ${script_path} echo "where am i:"$PWD deploy/script/generate_key.sh ${BAZEL_WORKSPACE_PATH} ${output_key_path} ${#iplist[@]} -deploy/script/generate_config.sh ${BAZEL_WORKSPACE_PATH} ${output_key_path} ${output_cert_path} ${output_path} ${admin_key_path} ${deploy_iplist[@]} +deploy/script/generate_config.sh ${BAZEL_WORKSPACE_PATH} ${output_key_path} ${output_cert_path} ${output_path} ${admin_key_path} ${client_num} ${deploy_iplist[@]} # build kv server -bazel build ${server} +bazel build ${server} if [ $? != 0 ] then @@ -62,7 +70,8 @@ function run_cmd(){ count=1 for ip in ${deploy_iplist[@]}; do - ssh -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no ubuntu@${ip} "$1" & + ssh $ssh_options_cloud -p 2222 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "$1" & + # ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "$1" & ((count++)) done @@ -73,11 +82,13 @@ function run_cmd(){ } function run_one_cmd(){ - ssh -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no ubuntu@${ip} "$1" + ssh $ssh_options_cloud -p 2222 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "$1" + # ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "$1" } run_cmd "killall -9 ${server_bin}" -run_cmd "rm -rf ${server_bin}; rm ${server_bin}*.log; rm -rf server.config; rm -rf cert;" +run_cmd "rm -rf ${server_bin}; rm ${server_bin}*.log; rm -rf server.config; rm -rf cert; rm -rf wal_log" +#run_cmd "rm -rf ${server_bin}; rm -rf server.config;" sleep 1 @@ -86,14 +97,23 @@ echo "upload configs" count=0 for ip in ${deploy_iplist[@]}; do - scp -i ${key} -r ${bin_path} ${output_path}/server.config ${output_path}/cert ubuntu@${ip}:/home/ubuntu/ & + # scp -i ${key} -r ${bin_path} ${output_path}/server.config ${output_path}/cert ${enclave_path} root@${ip}:/root/ > null 2>&1 & + scp $ssh_options_cloud -P 2222 -i ${key} -r ${bin_path} ${output_path}/server.config ${output_path}/cert ${enclave_path} root@${ip}:/root/ > null 2>&1 & + # scp $ssh_options_cloud -i ${key} -r ${bin_path} ${output_path}/server.config ${output_path}/cert ${enclave_path} root@${ip}:/root/ > null 2>&1 & ((count++)) done +wait -while [ $count -gt 0 ]; do - wait $pids - count=`expr $count - 1` -done +# while [ $count -gt 0 ]; do +# wait $pids +# count=`expr $count - 1` +# done + +# for ip in ${deploy_iplist[@]}; +# do +# ssh $ssh_options_cloud -p 22 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "tc qdisc add dev eth0 root netem delay 40ms 0ms" & +# done +# wait echo "start to run" # Start server @@ -103,16 +123,20 @@ for ip in ${deploy_iplist[@]}; do private_key="cert/node_"${idx}".key.pri" cert="cert/cert_"${idx}".cert" - run_one_cmd "nohup ./${server_bin} server.config ${private_key} ${cert} ${grafna_port} > ${server_bin}.log 2>&1 &" & + enclave="./enclave.signed" + echo "nohup ./${server_bin} server.config ${private_key} ${cert} ${enclave} --simulate" + run_one_cmd "nohup ./${server_bin} server.config ${private_key} ${cert} ${enclave} --simulate > ~/${server_bin}.log 2>&1 &" & ((count++)) ((idx++)) done +wait -while [ $count -gt 0 ]; do - wait $pids - count=`expr $count - 1` -done +# while [ $count -gt 0 ]; do +# wait $pids +# count=`expr $count - 1` +# done +echo "Check ready logs" # Check ready logs idx=1 for ip in ${deploy_iplist[@]}; @@ -120,7 +144,9 @@ do resp="" while [ "$resp" = "" ] do - resp=`ssh -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no ubuntu@${ip} "grep \"receive public size:${#iplist[@]}\" ${server_bin}.log"` + # echo ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "grep \"receive public size:${#iplist[@]}\" ${server_bin}.log" + resp=`ssh $ssh_options_cloud -p 2222 -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "grep \"receive public size:${#iplist[@]}\" ${server_bin}.log"` + # resp=`ssh $ssh_options_cloud -i ${key} -n -o BatchMode=yes -o StrictHostKeyChecking=no root@${ip} "grep \"receive public size:${#iplist[@]}\" ${server_bin}.log"` if [ "$resp" = "" ]; then sleep 1 fi diff --git a/scripts/deploy/script/env.sh b/scripts/deploy/script/env.sh index 8b03b76b5..71fee1d58 100755 --- a/scripts/deploy/script/env.sh +++ b/scripts/deploy/script/env.sh @@ -3,6 +3,8 @@ set +e CURRENT_PATH=$PWD +ssh_options_cloud='-o StrictHostKeyChecking=no -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=60' + i=0 while [ ! -f "WORKSPACE" ] do diff --git a/scripts/deploy/script/generate_config.sh b/scripts/deploy/script/generate_config.sh index 8f3db7625..b396c00fb 100755 --- a/scripts/deploy/script/generate_config.sh +++ b/scripts/deploy/script/generate_config.sh @@ -3,7 +3,7 @@ key_path=$1; shift output_cert_path=$1; shift output_path=$1; shift admin_key_path=$1; shift - +client_num=$1; shift iplist=$@ echo "generage certificates" @@ -12,19 +12,30 @@ echo "base path:"$base_path echo "key path:"$key_path echo "output cert path:"$output_cert_path echo "output path:"$output_path -echo "admin_key_path:"$admin_key_path +echo "admin_key_path?:"$admin_key_path +echo "tempalte path:",${TEMPLATE_PATH} + +if [ "${TEMPLATE_PATH}" = "" ] +then + TEMPLATE_PATH="../config/template.config" +fi + +echo "tempalte path:",${TEMPLATE_PATH} cd ${output_path} ADMIN_PRIVATE_KEY=${admin_key_path}/admin.key.pri ADMIN_PUBLIC_KEY=${admin_key_path}/admin.key.pub + + + CERT_TOOLS_BIN=${base_path}/bazel-bin/tools/certificate_tools CONFIG_TOOLS_BIN=${base_path}/bazel-bin/tools/generate_region_config -USERNAME=ubuntu -BASE_PORT=17000 -CLIENT_NUM=1 +USERNAME=root +BASE_PORT=10000 +CLIENT_NUM=${client_num} echo "" > client.config echo "" > server.config @@ -40,7 +51,7 @@ do done echo "node num:"$tot - +client_idx=1 for ip in ${iplist[@]}; do port=$((${BASE_PORT}+${idx})) @@ -52,6 +63,8 @@ do if [ $(($idx+$CLIENT_NUM)) -gt $tot ] ; then $CERT_TOOLS_BIN ${output_cert_path} ${ADMIN_PRIVATE_KEY} ${ADMIN_PUBLIC_KEY} ${public_key} ${idx} ${ip} ${port} client echo "${idx} ${ip} ${port}" >> client.config + echo "${idx} ${ip} ${port}" >> client${client_idx}.config + client_idx=$((client_idx+1)) else $CERT_TOOLS_BIN ${output_cert_path} ${ADMIN_PRIVATE_KEY} ${ADMIN_PUBLIC_KEY} ${public_key} ${idx} ${ip} ${port} replica echo "${idx} ${ip} ${port}" >> server.config @@ -60,5 +73,5 @@ do idx=$(($idx+1)) done -python3 ${CONFIG_TOOLS_BIN} ./server.config ./server.config.json +python3 ${CONFIG_TOOLS_BIN} ./server.config ./server.config.json ${TEMPLATE_PATH} mv server.config.json server.config diff --git a/scripts/deploy/script/load_config.sh b/scripts/deploy/script/load_config.sh index ff9ceaccc..85abfddda 100755 --- a/scripts/deploy/script/load_config.sh +++ b/scripts/deploy/script/load_config.sh @@ -1,2 +1,8 @@ - +KEY_FILE="config/key.conf" . $1 + +if [ ! -f "${KEY_FILE}" ]; then +"please create \"${KEY_FILE}\" and put your ssh key in it" +exit -1 +fi +. ${KEY_FILE} diff --git a/scripts/deploy/script/prepare.sh b/scripts/deploy/script/prepare.sh new file mode 100755 index 000000000..eff024677 --- /dev/null +++ b/scripts/deploy/script/prepare.sh @@ -0,0 +1,20 @@ +#export COPTS="--define enable_leveldb=True" + +ssh_options_cloud='-o StrictHostKeyChecking=no -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=60' + + +#!/bin/bash +eval "$(cat $1)" + +echo "IP List:" +for ip in "${iplist[@]}"; do + echo "$ip" +done + +for ip in ${iplist[@]}; +do + ssh $ssh_options_cloud -p 2222 root@$ip 'echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee -a /etc/apt/sources.list && sudo apt update && sudo apt install openssl=1.1.1f-1ubuntu2.22 --allow-downgrades -y' & + # ssh $ssh_options_cloud root@$ip 'echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee -a /etc/apt/sources.list && sudo apt update && sudo apt install openssl=1.1.1f-1ubuntu2.22 --allow-downgrades -y' & +done +wait + diff --git a/scripts/deploy/start_evaluation.sh b/scripts/deploy/start_evaluation.sh new file mode 100755 index 000000000..398f229b1 --- /dev/null +++ b/scripts/deploy/start_evaluation.sh @@ -0,0 +1,56 @@ +#!/bin/bash + +ssh_options_cloud='-o StrictHostKeyChecking=no -o LogLevel=ERROR -o UserKnownHostsFile=/dev/null -o ServerAliveInterval=60' + + +# cat < config/kv_performance_server.conf + +# iplist=( +# 172.17.0.2 +# 172.18.218.170 +# ) + +# client_num=1 +# EOF + +source config/kv_performance_server.conf + +echo "IP List:" +for ip in "${iplist[@]}"; do + echo "$ip" +done + +# for ip in ${iplist[@]}; +# do +# ssh $ssh_options_cloud root@$ip 'wget http://nz2.archive.ubuntu.com/ubuntu/pool/main/o/openssl/libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb && sudo dpkg -i libssl1.1_1.1.1f-1ubuntu2.23_amd64.deb' & +# # ssh $ssh_options_cloud root@$ip "sudo ip addr add $ip/32 dev eth0" & +# # echo "sudo ip addr add $ip/32 dev eth0" +# # ssh $ssh_options_cloud root@$ip 'echo "deb http://security.ubuntu.com/ubuntu focal-security main" | sudo tee -a /etc/apt/sources.list && sudo apt update && sudo apt install openssl=1.1.1f-1ubuntu2.22 --allow-downgrades -y' & +# done +# wait + +mode=$1 +if [[ "$mode" != "sgx" && "$mode" != "simulate" ]]; then + echo "Invalid mode. Please specify 'sgx' or 'simulate'." + exit 1 +fi + +deploy_file='./script/deploy.sh' +if [[ "$mode" == "sgx" ]]; then + if grep -q '${enclave} --simulate' "$deploy_file"; then + sed -i 's/${enclave} --simulate/${enclave}/g' "$deploy_file" + fi +else + if ! grep -q '${enclave} --simulate' "$deploy_file"; then + sed -i 's/${enclave}/${enclave} --simulate/g' "$deploy_file" + fi +fi + +./performance/fides_performance.sh config/kv_performance_server.conf +# ./performance/run_performance.sh config/kv_performance_server.conf + + + +# ldconfig /usr/local/lib64/ -> +# libssl.so.3: cannot open shared object file: No such file or directory +# apt-get install linux-headers-$(uname -r) diff --git a/scripts/null b/scripts/null new file mode 100644 index 000000000..e69de29bb diff --git a/service/contract/contract_service.cpp b/service/contract/contract_service.cpp index 22833c27b..73826af9d 100644 --- a/service/contract/contract_service.cpp +++ b/service/contract/contract_service.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include 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..3f283419b 100644 --- a/service/kv/kv_service.cpp +++ b/service/kv/kv_service.cpp @@ -1,64 +1,57 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #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,12 +59,13 @@ int main(int argc, char** argv) { ShowUsage(); exit(0); } + google::InitGoogleLogging(argv[0]); char* config_file = argv[1]; char* private_key_file = argv[2]; char* cert_file = argv[3]; - if (argc == 5) { + if (argc == 6) { std::string grafana_port = argv[4]; std::string grafana_address = "0.0.0.0:" + grafana_port; @@ -84,8 +78,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 87c9b37ee..20a8b7711 100644 --- a/service/tools/config/server/server.config +++ b/service/tools/config/server/server.config @@ -27,17 +27,12 @@ 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, - recovery_enabled : true, - recovery_path : "./log/", - recovery_buffer_size : 1024, } diff --git a/service/tools/contract/api_tools/contract_tools.cpp b/service/tools/contract/api_tools/contract_tools.cpp index 430e9e5bc..0883db4ed 100644 --- a/service/tools/contract/api_tools/contract_tools.cpp +++ b/service/tools/contract/api_tools/contract_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/service/tools/contract/service_tools/start_contract_service.sh b/service/tools/contract/service_tools/start_contract_service.sh index 7eccb0234..dad26d75d 100755 --- a/service/tools/contract/service_tools/start_contract_service.sh +++ b/service/tools/contract/service_tools/start_contract_service.sh @@ -1,11 +1,11 @@ -killall -9 contract_server +killall -9 contract_service -SERVER_PATH=./bazel-bin/service/contract/server/contract_server +SERVER_PATH=./bazel-bin/service/contract/contract_service SERVER_CONFIG=service/tools/config/server/server.config WORK_PATH=$PWD CERT_PATH=${WORK_PATH}/service/tools/data/cert/ -bazel build //service/contract/server:contract_server +bazel build //service/contract:contract_service nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node1.key.pri $CERT_PATH/cert_1.cert > server0.log & nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node2.key.pri $CERT_PATH/cert_2.cert > server1.log & nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node3.key.pri $CERT_PATH/cert_3.cert > server2.log & diff --git a/service/tools/kv/api_tools/kv_client_txn_tools.cpp b/service/tools/kv/api_tools/kv_client_txn_tools.cpp index d73271d1e..4ebff4de3 100644 --- a/service/tools/kv/api_tools/kv_client_txn_tools.cpp +++ b/service/tools/kv/api_tools/kv_client_txn_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/service/tools/kv/api_tools/kv_service_tools.cpp b/service/tools/kv/api_tools/kv_service_tools.cpp index f461e42c5..b5cd53444 100644 --- a/service/tools/kv/api_tools/kv_service_tools.cpp +++ b/service/tools/kv/api_tools/kv_service_tools.cpp @@ -1,29 +1,24 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include +#include #include #include #include @@ -40,13 +35,39 @@ using resdb::KVClient; using resdb::ReplicaInfo; using resdb::ResDBConfig; -int main(int argc, char** argv) { - if (argc < 3) { - printf( - " (set/get/getvalues/getrange), [key] " - "[value/key2]\n"); - return 0; - } +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'}, +}; + +void OldAPI(char** argv) { std::string client_config_file = argv[1]; std::string cmd = argv[2]; std::string key; @@ -80,7 +101,7 @@ int main(int argc, char** argv) { printf("client get value fail\n"); } } else if (cmd == "getvalues") { - auto res = client.GetValues(); + auto res = client.GetAllValues(); if (res != nullptr) { printf("client getvalues value = %s\n", res->c_str()); } else { @@ -95,3 +116,165 @@ int main(int argc, char** argv) { } } } + +int main(int argc, char** argv) { + std::string key; + int version = -1; + int option_index = 0; + int min_version = -1, max_version = -1; + std::string min_key, max_key; + std::string value; + std::string client_config_file; + int top = 0; + char c; + std::string cmd; + + if (argc >= 3) { + cmd = argv[2]; + if (cmd == "get" || cmd == "set" || cmd == "getvalues" || + cmd == "getrange") { + OldAPI(argv); + return 0; + } + } + + 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_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("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("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("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; + } + auto res = client.GetKeyHistory(key, min_version, max_version); + if (res != nullptr) { + 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( + "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; + } + 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("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("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/service_tools/start_utxo_service.sh b/service/tools/utxo/service_tools/start_utxo_service.sh index a0a7d5fc6..75b394f37 100755 --- a/service/tools/utxo/service_tools/start_utxo_service.sh +++ b/service/tools/utxo/service_tools/start_utxo_service.sh @@ -1,13 +1,13 @@ -killall -9 utxo_server +killall -9 utxo_service -SERVER_PATH=./bazel-bin/service/utxo/server/utxo_server +SERVER_PATH=./bazel-bin/service/utxo/utxo_service SERVER_CONFIG=service/tools/config/server/server.config WORK_PATH=$PWD CERT_PATH=${WORK_PATH}/service/tools/data/cert/ UTXO_CONFIG=service/tools/config/server/utxo_config.config -bazel build //service/utxo/server:utxo_server +bazel build //service/utxo:utxo_service nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node1.key.pri $CERT_PATH/cert_1.cert ${UTXO_CONFIG} > server0.log & nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node2.key.pri $CERT_PATH/cert_2.cert ${UTXO_CONFIG} > server1.log & nohup $SERVER_PATH $SERVER_CONFIG $CERT_PATH/node3.key.pri $CERT_PATH/cert_3.cert ${UTXO_CONFIG} > server2.log & 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 207b9233c..596b468c3 100644 --- a/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp +++ b/service/tools/utxo/wallet_tool/cpp/utxo_client_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include @@ -46,9 +40,11 @@ void ShowUsage() { } void Transfer(UTXOClient* client, int64_t transaction_id, - const std::string& address, const std::string& to_address, - const int value, const std::string& private_key, - const std::string& to_pub_key) { + const std::string& address, + const std::vector& to_address, + 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"); return; @@ -60,13 +56,17 @@ void Transfer(UTXOClient* client, int64_t transaction_id, in->set_out_idx(0); nonce += transaction_id; - UTXOOut* out = utxo.add_out(); - out->set_address(to_address); - out->set_value(value); - out->set_pub_key(to_pub_key); - utxo.set_address(address); - utxo.set_sig(resdb::utils::ECDSASignString(private_key, - address + std::to_string(nonce))); + 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:" << address << " to:" << to_address[i] + << " value:" << values[i]; + } auto output = client->Transfer(utxo); LOG(ERROR) << "execute result:\n" << output; @@ -87,6 +87,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 ret; + while (true) { + size_t pos = str.find(","); + if (pos == std::string::npos) { + ret.push_back(str); + break; + } + ret.push_back(str.substr(0, pos)); + str = str.substr(pos + 1); + } + return ret; +} + +std::vector ParseValue(std::string str) { + std::vector ret; + while (true) { + size_t pos = str.find(","); + 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); + } + return ret; +} + int main(int argc, char** argv) { if (argc < 3) { printf("-d -c [config]\n"); @@ -100,7 +128,7 @@ int main(int argc, char** argv) { int num = 10; int c; std::string cmd; - int64_t value = 0; + std::string value; std::string client_config_file; while ((c = getopt(argc, argv, "c:d:t:x:m:h:e:v:n:p:b:")) != -1) { switch (c) { @@ -126,7 +154,7 @@ int main(int argc, char** argv) { num = strtoull(optarg, NULL, 10); break; case 'v': - value = strtoull(optarg, NULL, 10); + value = optarg; break; case 'p': private_key = optarg; @@ -142,11 +170,10 @@ int main(int argc, char** argv) { ResDBConfig config = GenerateResDBConfig(client_config_file); config.SetClientTimeoutMs(100000); - UTXOClient client(config); if (cmd == "transfer") { - Transfer(&client, transaction_id, address, to_address, value, private_key, - 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") { diff --git a/service/tools/utxo/wallet_tool/py/addr.py b/service/tools/utxo/wallet_tool/py/addr.py index f4cae5eb1..5b64b29f3 100644 --- a/service/tools/utxo/wallet_tool/py/addr.py +++ b/service/tools/utxo/wallet_tool/py/addr.py @@ -1,24 +1,19 @@ -# 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. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. import sys import wallet_tools_py diff --git a/service/tools/utxo/wallet_tool/py/keys.py b/service/tools/utxo/wallet_tool/py/keys.py index 72c4fdb74..c90ea9c33 100644 --- a/service/tools/utxo/wallet_tool/py/keys.py +++ b/service/tools/utxo/wallet_tool/py/keys.py @@ -1,24 +1,20 @@ -# 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. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import wallet_tools_py diff --git a/service/tools/utxo/wallet_tool/test/key_tester.py b/service/tools/utxo/wallet_tool/test/key_tester.py index ca06f1409..f9112c509 100644 --- a/service/tools/utxo/wallet_tool/test/key_tester.py +++ b/service/tools/utxo/wallet_tool/test/key_tester.py @@ -1,24 +1,19 @@ -# 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. +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. import wallet_tools_py import key_tester_utils diff --git a/service/tools/utxo/wallet_tool/test/key_tester_utils.cpp b/service/tools/utxo/wallet_tool/test/key_tester_utils.cpp index af7de2c80..d1744fb6c 100644 --- a/service/tools/utxo/wallet_tool/test/key_tester_utils.cpp +++ b/service/tools/utxo/wallet_tool/test/key_tester_utils.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/service/utils/server_factory.cpp b/service/utils/server_factory.cpp index 61841cac9..377f03096 100644 --- a/service/utils/server_factory.cpp +++ b/service/utils/server_factory.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include "service/utils/server_factory.h" diff --git a/service/utils/server_factory.h b/service/utils/server_factory.h index 5ab7364d0..aa23254f4 100644 --- a/service/utils/server_factory.h +++ b/service/utils/server_factory.h @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #pragma once diff --git a/service/utxo/utxo_service.cpp b/service/utxo/utxo_service.cpp index 7c5bfcc9c..c231526f1 100644 --- a/service/utxo/utxo_service.cpp +++ b/service/utxo/utxo_service.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/tools/certificate_tools.cpp b/tools/certificate_tools.cpp index 6be804f70..92a24eae3 100644 --- a/tools/certificate_tools.cpp +++ b/tools/certificate_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/tools/certificate_tools_test.cpp b/tools/certificate_tools_test.cpp index 8748880af..42bd31652 100644 --- a/tools/certificate_tools_test.cpp +++ b/tools/certificate_tools_test.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/tools/generate_mulregion_config.py b/tools/generate_mulregion_config.py index 424a343cc..e25642f87 100644 --- a/tools/generate_mulregion_config.py +++ b/tools/generate_mulregion_config.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import os import json import sys diff --git a/tools/generate_region_config.py b/tools/generate_region_config.py index 96f0d8f92..c3d001f22 100644 --- a/tools/generate_region_config.py +++ b/tools/generate_region_config.py @@ -1,3 +1,20 @@ +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. + import os import json import sys @@ -6,10 +23,8 @@ from google.protobuf.json_format import Parse, ParseDict -def GenerateJsonConfig(file_name, output_file): +def GenerateJsonConfig(file_name, output_file, template_file): config_data=ResConfigData() - config_data.enable_viewchange = False - config_data.max_client_complaint_num = 10 tmp_config={} with open(file_name) as f: for line in f.readlines(): @@ -39,5 +54,31 @@ def GenerateJsonConfig(file_name, output_file): with open(output_file,"w") as f: f.write(json_obj) + if template_file: + old_json = None + with open(output_file) as f: + lines=f.readlines() + for l in lines: + l=l.strip() + s=''.join(lines) + old_json=json.loads(s) + + template_json = {} + with open(template_file) as f: + lines=f.readlines() + for l in lines: + l=l.strip() + s=''.join(lines) + template_json=json.loads(s) + + for (k,v) in template_json.items(): + old_json[k] = v + + with open(output_file,"w") as f: + json.dump(old_json, f) + if __name__ == "__main__": - GenerateJsonConfig(sys.argv[1], sys.argv[2]) + template_config = None + if len(sys.argv)>3: + template_config = sys.argv[3] + GenerateJsonConfig(sys.argv[1], sys.argv[2], template_config) diff --git a/tools/key_generator_tools.cpp b/tools/key_generator_tools.cpp index 80c063324..93b15d9cd 100644 --- a/tools/key_generator_tools.cpp +++ b/tools/key_generator_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/tools/resdb_state_accessor_tools.cpp b/tools/resdb_state_accessor_tools.cpp index 8aaa45d4c..59720cfe1 100644 --- a/tools/resdb_state_accessor_tools.cpp +++ b/tools/resdb_state_accessor_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include diff --git a/tools/resdb_txn_accessor_tools.cpp b/tools/resdb_txn_accessor_tools.cpp index 35cc37c0d..38cb2a408 100644 --- a/tools/resdb_txn_accessor_tools.cpp +++ b/tools/resdb_txn_accessor_tools.cpp @@ -1,26 +1,20 @@ /* - * Copyright (c) 2019-2022 ExpoLab, UC Davis + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at * - * 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. + * http://www.apache.org/licenses/LICENSE-2.0 * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. */ #include