diff --git a/.gitignore b/.gitignore index 7b5e22f8c3..6c068cb695 100644 --- a/.gitignore +++ b/.gitignore @@ -96,3 +96,12 @@ compile_commands.json .vscode .history cmake-build-debug + +# Files from running isolated server +persistence/ +scilla/ +scilla_files/ +evm-ds/ +config.xml +isolated-server-accounts.json +isolated-server.logs diff --git a/CMakeLists.txt b/CMakeLists.txt index 42ff055afc..8858a69b9a 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -45,6 +45,8 @@ find_package(g3logger CONFIG REQUIRED) include(InstallCryptoUtils) +include(ProjectSecp256k1) + include(InstallMongo) find_package(mongocxx CONFIG REQUIRED) diff --git a/README.md b/README.md index 4adf69bd8c..73e97358f5 100644 --- a/README.md +++ b/README.md @@ -106,6 +106,7 @@ export PATH=$HOME/.local/bin:$PATH cmake --version rm cmake-3.19.3-Linux-x86_64.sh ``` + ### Additional Requirements for Contributors If you intend to contribute to the code base, please perform these additional steps: @@ -168,7 +169,7 @@ The Zilliqa client works together with Scilla for executing smart contracts. Ple $ cd build && ./tests/Node/pre_run.sh && ./tests/Node/test_node_lookup.sh && ./tests/Node/test_node_simple.sh ``` -2. Logs of each node can be found at `./local_run`. +2. Logs of each node can be found at `./local_run` 3. To terminate Zilliqa: diff --git a/cmake/ProjectSecp256k1.cmake b/cmake/ProjectSecp256k1.cmake new file mode 100644 index 0000000000..21f74b972c --- /dev/null +++ b/cmake/ProjectSecp256k1.cmake @@ -0,0 +1,39 @@ +include(ExternalProject) + +if (MSVC) + set(_only_release_configuration -DCMAKE_CONFIGURATION_TYPES=Release) + set(_overwrite_install_command INSTALL_COMMAND cmake --build --config Release --target install) +endif() + +set(prefix "${CMAKE_BINARY_DIR}/deps") +set(SECP256K1_LIBRARY "${prefix}/lib/${CMAKE_STATIC_LIBRARY_PREFIX}secp256k1${CMAKE_STATIC_LIBRARY_SUFFIX}") +set(SECP256K1_INCLUDE_DIR "${prefix}/include") + +ExternalProject_Add( + secp256k1 + PREFIX "${prefix}" + DOWNLOAD_NAME secp256k1-ac8ccf29.tar.gz + DOWNLOAD_NO_PROGRESS 1 + URL https://github.com/chfast/secp256k1/archive/ac8ccf29b8c6b2b793bc734661ce43d1f952977a.tar.gz + URL_HASH SHA256=02f8f05c9e9d2badc91be8e229a07ad5e4984c1e77193d6b00e549df129e7c3a + PATCH_COMMAND ${CMAKE_COMMAND} -E copy_if_different + ${CMAKE_CURRENT_LIST_DIR}/secp256k1/CMakeLists.txt + CMAKE_ARGS -DCMAKE_INSTALL_PREFIX= + -DCMAKE_POSITION_INDEPENDENT_CODE=${BUILD_SHARED_LIBS} + -DCMAKE_C_COMPILER=${CMAKE_C_COMPILER} + -DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER} + ${_only_release_configuration} + LOG_CONFIGURE 1 + BUILD_COMMAND "" + ${_overwrite_install_command} + LOG_INSTALL 1 + BUILD_BYPRODUCTS "${SECP256K1_LIBRARY}" +) + +# Create imported library +add_library(Secp256k1 STATIC IMPORTED) +file(MAKE_DIRECTORY "${SECP256K1_INCLUDE_DIR}") # Must exist. +set_property(TARGET Secp256k1 PROPERTY IMPORTED_CONFIGURATIONS Release) +set_property(TARGET Secp256k1 PROPERTY IMPORTED_LOCATION_RELEASE "${SECP256K1_LIBRARY}") +set_property(TARGET Secp256k1 PROPERTY INTERFACE_INCLUDE_DIRECTORIES "${SECP256K1_INCLUDE_DIR}") +add_dependencies(Secp256k1 secp256k1) diff --git a/cmake/secp256k1/CMakeLists.txt b/cmake/secp256k1/CMakeLists.txt new file mode 100644 index 0000000000..467d2f7c26 --- /dev/null +++ b/cmake/secp256k1/CMakeLists.txt @@ -0,0 +1,38 @@ +# This CMake config file for secp256k1 project from https://github.com/bitcoin-core/secp256k1 +# +# The secp256k1 project has been configured following official docs with following options: +# +# ./configure --disable-shared --disable-tests --disable-coverage --disable-openssl-tests --disable-exhaustive-tests --disable-jni --with-bignum=no --with-field=64bit --with-scalar=64bit --with-asm=no +# +# Build static context: +# make src/ecmult_static_context.h +# +# Copy src/ecmult_static_context.h and src/libsecp256k1-config.h +# +# Copy CFLAGS from Makefile to COMPILE_OPTIONS. + +cmake_minimum_required(VERSION 3.4) +project(secp256k1 LANGUAGES C) + +set(COMMON_COMPILE_FLAGS ENABLE_MODULE_RECOVERY ENABLE_MODULE_ECDH USE_ECMULT_STATIC_PRECOMPUTATION USE_FIELD_INV_BUILTIN USE_NUM_NONE USE_SCALAR_INV_BUILTIN) +if (MSVC) + set(COMPILE_FLAGS USE_FIELD_10X26 USE_SCALAR_8X32) + set(COMPILE_OPTIONS "") +else() + set(COMPILE_FLAGS USE_FIELD_5X52 USE_SCALAR_4X64 HAVE_BUILTIN_EXPECT HAVE___INT128) + set(COMPILE_OPTIONS -O3 -W -std=c89 -pedantic -Wall -Wextra -Wcast-align -Wnested-externs -Wshadow -Wstrict-prototypes -Wno-unused-function -Wno-long-long -Wno-overlength-strings -fvisibility=hidden) +endif() + +add_executable(gen_context src/gen_context.c) +target_include_directories(gen_context PRIVATE ${CMAKE_SOURCE_DIR}) + +add_custom_target(ecmult_static_context gen_context WORKING_DIRECTORY ${CMAKE_SOURCE_DIR}) + +add_library(secp256k1 STATIC src/secp256k1.c) +target_compile_definitions(secp256k1 PRIVATE ${COMMON_COMPILE_FLAGS} ${COMPILE_FLAGS}) +target_include_directories(secp256k1 PRIVATE ${CMAKE_SOURCE_DIR} ${CMAKE_SOURCE_DIR}/src) +target_compile_options(secp256k1 PRIVATE ${COMPILE_OPTIONS}) +add_dependencies(secp256k1 ecmult_static_context) + +install(TARGETS secp256k1 ARCHIVE DESTINATION lib) +install(DIRECTORY include/ DESTINATION include) diff --git a/docker/Dockerfile b/docker/Dockerfile index d68ef9fb02..224a25c3bc 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -98,39 +98,39 @@ ARG BUILD_TYPE=RelWithDebInfo ARG EXTRA_CMAKE_ARGS= ARG MONGO_INSTALL_DIR=${BUILD_DIR}/mongo -RUN git clone ${REPO} ${SOURCE_DIR} \ - && git -C ${SOURCE_DIR} checkout ${COMMIT_OR_TAG} \ - && cmake -H${SOURCE_DIR} -B${BUILD_DIR} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} \ - -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} ${EXTRA_CMAKE_ARGS} \ - && cmake --build ${BUILD_DIR} -- -j$(nproc) \ - && cmake --build ${BUILD_DIR} --target install \ - && echo "built files:" && ls -lh ${BUILD_DIR} && echo "installed libs:" && ls -lh ${INSTALL_DIR}/lib \ - && echo "mongo files:" && ls -lh ${MONGO_INSTALL_DIR}/lib \ - # strip all exes - && strip /usr/local/bin/grepperf \ - /usr/local/bin/zilliqad \ - /usr/local/bin/genkeypair \ - /usr/local/bin/signmultisig \ - /usr/local/bin/verifymultisig \ - /usr/local/bin/getpub \ - /usr/local/bin/getaddr \ - /usr/local/bin/genaccounts \ - /usr/local/bin/sendcmd \ - /usr/local/bin/gentxn \ - /usr/local/bin/restore \ - /usr/local/bin/gensigninitialds \ - /usr/local/bin/validateDB \ - /usr/local/bin/genTxnBodiesFromS3 \ - /usr/local/bin/getnetworkhistory \ - /usr/local/bin/isolatedServer \ - /usr/local/bin/getrewardhistory \ - # /usr/local/bin/zilliqa \ - /usr/local/bin/data_migrate \ - /usr/local/lib/libSchnorr.so \ - /usr/local/lib/libCryptoUtils.so \ - /usr/local/lib/libNAT.so \ - /usr/local/lib/libCommon.so \ - /usr/local/lib/libTrie.so +RUN git clone ${REPO} ${SOURCE_DIR} +RUN git -C ${SOURCE_DIR} checkout ${COMMIT_OR_TAG} +RUN cmake -H${SOURCE_DIR} -B${BUILD_DIR} -DCMAKE_BUILD_TYPE=${BUILD_TYPE} -DCMAKE_INSTALL_PREFIX=${INSTALL_DIR} ${EXTRA_CMAKE_ARGS} +RUN cmake --build ${BUILD_DIR} -- -j$(nproc) +RUN cmake --build ${BUILD_DIR} --target install +RUN echo "built files:" && ls -lh ${BUILD_DIR} && echo "installed libs:" && ls -lh ${INSTALL_DIR}/lib +RUN echo "mongo files:" && ls -lh ${MONGO_INSTALL_DIR}/lib + +# strip all exes +RUN strip /usr/local/bin/grepperf \ + /usr/local/bin/zilliqad \ + /usr/local/bin/genkeypair \ + /usr/local/bin/signmultisig \ + /usr/local/bin/verifymultisig \ + /usr/local/bin/getpub \ + /usr/local/bin/getaddr \ + /usr/local/bin/genaccounts \ + /usr/local/bin/sendcmd \ + /usr/local/bin/gentxn \ + /usr/local/bin/restore \ + /usr/local/bin/gensigninitialds \ + /usr/local/bin/validateDB \ + /usr/local/bin/genTxnBodiesFromS3 \ + /usr/local/bin/getnetworkhistory \ + /usr/local/bin/isolatedServer \ + /usr/local/bin/getrewardhistory \ +# /usr/local/bin/zilliqa \ + /usr/local/bin/data_migrate \ + /usr/local/lib/libSchnorr.so \ + /usr/local/lib/libCryptoUtils.so \ + /usr/local/lib/libNAT.so \ + /usr/local/lib/libCommon.so \ + /usr/local/lib/libTrie.so # start from a new ubuntu as the runner image FROM ubuntu:18.04 @@ -182,7 +182,7 @@ RUN pip3 install wheel \ && pip3 install --no-cache-dir -r requirements3.txt \ && update-alternatives --install /usr/bin/python python /usr/bin/python3 10 # set python3 as default instead python2 -# make dirs fro scilla and zilliqa +# make dirs for scilla and zilliqa RUN mkdir -p \ /scilla/0/bin /scilla/0/src/stdlib \ /zilliqa/scripts diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 1698cc6773..e9c6da2473 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -6,6 +6,7 @@ add_subdirectory (libConsensus) add_subdirectory (libCrypto) add_subdirectory (libData) add_subdirectory (libDirectoryService) +add_subdirectory (libEth) add_subdirectory (libLookup) add_subdirectory (libMediator) add_subdirectory (libMessage) diff --git a/src/common/Constants.cpp b/src/common/Constants.cpp index 6bdf99bc59..5b4161a775 100644 --- a/src/common/Constants.cpp +++ b/src/common/Constants.cpp @@ -136,6 +136,8 @@ const unsigned int MSG_VERSION{ ReadConstantNumeric("MSG_VERSION", "node.version.")}; const unsigned int TRANSACTION_VERSION{ ReadConstantNumeric("TRANSACTION_VERSION", "node.version.")}; +const unsigned int TRANSACTION_VERSION_ETH{ + ReadConstantNumeric("TRANSACTION_VERSION", "node.version.") + 1}; const unsigned int DSBLOCK_VERSION{ ReadConstantNumeric("DSBLOCK_VERSION", "node.version.")}; const unsigned int TXBLOCK_VERSION{ @@ -737,7 +739,6 @@ const vector> VERIFIER_MICROBLOCK_EXCLUSION_LIST{ ReadVerifierMicroblockExclusionListFromConstantsFile()}; const bool ENABLE_EVM{ ReadConstantString("ENABLE_EVM", "node.jsonrpc.", "true") == "true"}; - const std::string EVM_SERVER_SOCKET_PATH{ReadConstantString( "EVM_SERVER_SOCKET_PATH", "node.jsonrpc.", "/tmp/evm-server.sock")}; const std::string EVM_SERVER_BINARY{ReadConstantString( @@ -746,3 +747,5 @@ const std::string EVM_LOG_CONFIG{ReadConstantString( "EVM_LOG_CONFIG", "node.jsonrpc.", "/usr/local/etc/log4rs.yml")}; const std::string ETH_CHAINID{ ReadConstantString("ETH_CHAINID", "node.jsonrpc.", "0x814d")}; +const uint64_t ETH_CHAINID_INT{DataConversion::HexStringToUint64Ret( + ReadConstantString("ETH_CHAINID", "node.jsonrpc.", "0x814d"))}; \ No newline at end of file diff --git a/src/common/Constants.h b/src/common/Constants.h index 0a2987a80b..f686f0d7f7 100644 --- a/src/common/Constants.h +++ b/src/common/Constants.h @@ -163,6 +163,7 @@ extern const uint64_t INIT_TRIE_DB_SNAPSHOT_EPOCH; // Version constants extern const unsigned int MSG_VERSION; extern const unsigned int TRANSACTION_VERSION; +extern const unsigned int TRANSACTION_VERSION_ETH; extern const unsigned int DSBLOCK_VERSION; extern const unsigned int TXBLOCK_VERSION; extern const unsigned int MICROBLOCK_VERSION; @@ -310,6 +311,7 @@ extern const std::string EVM_SERVER_SOCKET_PATH; extern const std::string EVM_SERVER_BINARY; extern const std::string EVM_LOG_CONFIG; extern const std::string ETH_CHAINID; +extern const uint64_t ETH_CHAINID_INT; extern const std::string IP_TO_BIND; // Only for non-lookup nodes extern const bool ENABLE_STAKING_RPC; diff --git a/src/depends/Schnorr b/src/depends/Schnorr index 8d19e7b068..66b1ae3df0 160000 --- a/src/depends/Schnorr +++ b/src/depends/Schnorr @@ -1 +1 @@ -Subproject commit 8d19e7b0683ccafab1440644cec4d6b7e12af70c +Subproject commit 66b1ae3df0a602b16fcf48f893f39903ff0da2c2 diff --git a/src/libCrypto/CMakeLists.txt b/src/libCrypto/CMakeLists.txt index ba52f2c0d9..27c0983a52 100644 --- a/src/libCrypto/CMakeLists.txt +++ b/src/libCrypto/CMakeLists.txt @@ -1,2 +1,3 @@ add_library(EthCrypto EthCrypto.cpp) target_include_directories(EthCrypto PUBLIC ${PROJECT_SOURCE_DIR}/src) +target_link_libraries(EthCrypto Secp256k1) diff --git a/src/libCrypto/EthCrypto.cpp b/src/libCrypto/EthCrypto.cpp index 33c4a08670..036915d7ac 100644 --- a/src/libCrypto/EthCrypto.cpp +++ b/src/libCrypto/EthCrypto.cpp @@ -16,13 +16,18 @@ */ #include "EthCrypto.h" +#include "libUtils/DataConversion.h" #include "libUtils/Logger.h" +#include "depends/common/RLP.h" + +#include "secp256k1.h" +#include "secp256k1_recovery.h" + #include // for EC_GROUP_new_by_curve_name, EC_GROUP_free, EC_KEY_new, EC_KEY_set_group, EC_KEY_generate_key, EC_KEY_free #include // for NID_secp192k1 #include //for SHA512_DIGEST_LENGTH #include -#include #include #include #include @@ -69,16 +74,13 @@ void SetOpensslSignature(const std::string& sSignatureInHex, ECDSA_SIG* pSign) { bool SetOpensslPublicKey(const char* sPubKeyString, EC_KEY* pKey) { // X co-ordinate std::unique_ptr gx(NULL, bnFree); + std::unique_ptr gy(NULL, bnFree); BIGNUM* gx_ptr = gx.get(); + BIGNUM* gy_ptr = gy.get(); EC_KEY_set_asn1_flag(pKey, OPENSSL_EC_NAMED_CURVE); - // From - // https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html - // The first byte indicates whether the y coordinate is odd or even - int y_chooser_bit = 0; - if (sPubKeyString[0] != '0') { LOG_GENERAL(WARNING, "Received badly set signature bit! Should be 0 and got: " @@ -86,14 +88,23 @@ bool SetOpensslPublicKey(const char* sPubKeyString, EC_KEY* pKey) { return false; } + // From + // https://www.oreilly.com/library/view/mastering-ethereum/9781491971932/ch04.html + // The first byte indicates whether the y coordinate is odd or even + int y_chooser_bit = 0; + bool notCompressed = false; + if (sPubKeyString[1] == '2') { y_chooser_bit = 0; } else if (sPubKeyString[1] == '3') { y_chooser_bit = 1; + } else if (sPubKeyString[1] == '4') { + notCompressed = true; } else { - LOG_GENERAL(WARNING, - "Received badly set signature bit! Should be 2 or 3 and got: " - << sPubKeyString[1]); + LOG_GENERAL( + WARNING, + "Received badly set signature bit! Should be 2, 3 or 4 and got: " + << sPubKeyString[1] << " Note: signature is: " << sPubKeyString); } // Don't want the first byte @@ -110,8 +121,19 @@ bool SetOpensslPublicKey(const char* sPubKeyString, EC_KEY* pKey) { EC_POINT_new(curve_group.get()), epFree); // This performs the decompression at the same time as setting the pubKey - EC_POINT_set_compressed_coordinates_GFp(curve_group.get(), point.get(), - gx_ptr, y_chooser_bit, NULL); + if (notCompressed) { + LOG_GENERAL(WARNING, "Not compressed - setting..."); + + if (!BN_hex2bn(&gy_ptr, sPubKeyString + 2 + 64)) { + LOG_GENERAL(WARNING, "Error getting to y binary format"); + } + + EC_POINT_set_affine_coordinates(curve_group.get(), point.get(), gx_ptr, + gy_ptr, NULL); + } else { + EC_POINT_set_compressed_coordinates_GFp(curve_group.get(), point.get(), + gx_ptr, y_chooser_bit, NULL); + } if (!EC_KEY_set_public_key(pKey, point.get())) { LOG_GENERAL(WARNING, "ERROR! setting public key attributes"); @@ -125,7 +147,7 @@ bool SetOpensslPublicKey(const char* sPubKeyString, EC_KEY* pKey) { } } -bool VerifyEcdsaSecp256k1(const std::string& /*sRandomNumber*/, +bool VerifyEcdsaSecp256k1(const bytes& sRandomNumber, const std::string& sSignature, const std::string& sDevicePubKeyInHex) { std::unique_ptr zSignature(ECDSA_SIG_new(), @@ -140,10 +162,11 @@ bool VerifyEcdsaSecp256k1(const std::string& /*sRandomNumber*/, LOG_GENERAL(WARNING, "Failed to get the public key from the hex input"); } - auto result_prelude = ethash::keccak256(prelude, sizeof(prelude)); + auto const result = + ECDSA_do_verify(sRandomNumber.data(), SHA256_DIGEST_LENGTH, + zSignature.get(), zPublicKey.get()); - return ECDSA_do_verify(result_prelude.bytes, SHA256_DIGEST_LENGTH, - zSignature.get(), zPublicKey.get()); + return result; } // Given a hex string representing the pubkey (secp256k1), return the hex @@ -188,4 +211,121 @@ std::string ToUncompressedPubKey(std::string const& pubKey) { } return ret; -} \ No newline at end of file +} + +secp256k1_context const* getCtx() { + static std::unique_ptr + s_ctx{secp256k1_context_create(SECP256K1_CONTEXT_SIGN | + SECP256K1_CONTEXT_VERIFY), + &secp256k1_context_destroy}; + return s_ctx.get(); +} + +// EIP-155 : assume the chain height is high enough that the signing scheme +// is in line with EIP-155. +// message shall not contain '0x' +bytes RecoverECDSAPubSig(std::string const& message, int chain_id) { + // First we need to parse the RSV message, then set the last three fields + // to chain_id, 0, 0 in order to recreate what was signed + bytes asBytes; + DataConversion::HexStrToUint8Vec(message, asBytes); + + dev::RLP rlpStream1(asBytes); + dev::RLPStream rlpStreamRecreated(9); + + int i = 0; + int v = 0; + bytes rs; + + // Iterate through the RLP message and build up what the message was before + // it was hashed and signed. That is, same size, same fields, except + // v = chain_id, R and S = 0 + for (const auto& item : rlpStream1) { + auto itemBytes = item.operator bytes(); + + // First 5 fields stay the same + if (i <= 5) { + rlpStreamRecreated << itemBytes; + } + + // Field V + if (i == 6) { + rlpStreamRecreated << chain_id; + v = uint32_t(item); + } + + // Fields R and S + if (i == 7 || i == 8) { + rlpStreamRecreated << bytes{}; + rs.insert(rs.end(), itemBytes.begin(), itemBytes.end()); + } + i++; + } + + // Determine whether the rcid is 0,1 based on the V + int vSelect = (v - (chain_id * 2)); + vSelect = vSelect == 35 ? 0 : vSelect; + vSelect = vSelect == 36 ? 1 : vSelect; + + if (!(vSelect >= 0 && vSelect <= 3)) { + LOG_GENERAL(WARNING, "Received badly parsed recid in raw transaction: " + << v << " with chainID " << chain_id << " for " + << vSelect); + return {}; + } + + auto messageRecreatedBytes = rlpStreamRecreated.out(); + + // Sign original message + auto signingHash = ethash::keccak256(messageRecreatedBytes.data(), + messageRecreatedBytes.size()); + + // Load the RS into the library + auto* ctx = getCtx(); + secp256k1_ecdsa_recoverable_signature rawSig; + if (!secp256k1_ecdsa_recoverable_signature_parse_compact( + ctx, &rawSig, rs.data(), vSelect)) { + LOG_GENERAL(WARNING, + "Error getting RS signature during public key reconstruction"); + return {}; + } + + // Re-create public key given signature, and message + secp256k1_pubkey rawPubkey; + if (!secp256k1_ecdsa_recover(ctx, &rawPubkey, &rawSig, + &signingHash.bytes[0])) { + LOG_GENERAL(WARNING, + "Error recovering public key during public key reconstruction"); + return {}; + } + + // Parse the public key out of the library format + bytes serializedPubkey(65); + size_t serializedPubkeySize = serializedPubkey.size(); + secp256k1_ec_pubkey_serialize(ctx, serializedPubkey.data(), + &serializedPubkeySize, &rawPubkey, + SECP256K1_EC_UNCOMPRESSED); + + return serializedPubkey; +} + +// nonce, gasprice, startgas, to, value, data, chainid, 0, 0 +bytes GetOriginalHash(TransactionCoreInfo const& info, uint64_t chainId) { + dev::RLPStream rlpStreamRecreated(9); + + rlpStreamRecreated << info.nonce; + rlpStreamRecreated << info.gasPrice; + rlpStreamRecreated << info.gasLimit; + rlpStreamRecreated << info.toAddr; + rlpStreamRecreated << info.amount; + rlpStreamRecreated << info.data; + rlpStreamRecreated << chainId; + rlpStreamRecreated << bytes{}; + rlpStreamRecreated << bytes{}; + + auto const signingHash = ethash::keccak256(rlpStreamRecreated.out().data(), + rlpStreamRecreated.out().size()); + + return bytes{&signingHash.bytes[0], &signingHash.bytes[32]}; +} diff --git a/src/libCrypto/EthCrypto.h b/src/libCrypto/EthCrypto.h index 0d90845b32..4acf955284 100644 --- a/src/libCrypto/EthCrypto.h +++ b/src/libCrypto/EthCrypto.h @@ -18,13 +18,15 @@ #ifndef ZILLIQA_SRC_LIBCRYPTO_ETHCRYPTO_H_ #define ZILLIQA_SRC_LIBCRYPTO_ETHCRYPTO_H_ +#include #include // for ECDSA_do_sign, ECDSA_do_verify - #include +#include "common/BaseType.h" +#include "libData/AccountData/Transaction.h" constexpr unsigned int UNCOMPRESSED_SIGNATURE_SIZE = 65; -bool VerifyEcdsaSecp256k1(const std::string& sRandomNumber, +bool VerifyEcdsaSecp256k1(const bytes& sRandomNumber, const std::string& sSignature, const std::string& sDevicePubKeyInHex); @@ -34,4 +36,8 @@ bool VerifyEcdsaSecp256k1(const std::string& sRandomNumber, // per the 'Standards for Efficient Cryptography' specification std::string ToUncompressedPubKey(const std::string& pubKey); +bytes RecoverECDSAPubSig(std::string const& message, int chain_id); + +bytes GetOriginalHash(TransactionCoreInfo const& info, uint64_t chainId); + #endif // ZILLIQA_SRC_LIBCRYPTO_ETHCRYPTO_H_ diff --git a/src/libData/AccountData/Transaction.cpp b/src/libData/AccountData/Transaction.cpp index 5129a01cf9..4d16f0c664 100644 --- a/src/libData/AccountData/Transaction.cpp +++ b/src/libData/AccountData/Transaction.cpp @@ -147,6 +147,14 @@ const TransactionCoreInfo& Transaction::GetCoreInfo() const { const uint32_t& Transaction::GetVersion() const { return m_coreInfo.version; } +// Check if the version is 1 or 2 - the only valid ones for now +// this will look like 65538 or 65537 +bool Transaction::VersionCorrect() const { + auto version = DataConversion::UnpackB(this->GetVersion()); + + return (version == TRANSACTION_VERSION || version == TRANSACTION_VERSION_ETH); +} + const uint64_t& Transaction::GetNonce() const { return m_coreInfo.nonce; } const Address& Transaction::GetToAddr() const { return m_coreInfo.toAddr; } @@ -202,7 +210,9 @@ bool Transaction::IsSignedECDSA() const { sigString = sigString.substr(2); pubKeyStr = pubKeyStr.substr(2); - return VerifyEcdsaSecp256k1("", sigString, pubKeyStr); + auto const hash = GetOriginalHash(GetCoreInfo(), ETH_CHAINID_INT); + + return VerifyEcdsaSecp256k1(hash, sigString, pubKeyStr); } // Function to return whether the TX is signed diff --git a/src/libData/AccountData/Transaction.h b/src/libData/AccountData/Transaction.h index 59d75dc960..8bdc704a37 100644 --- a/src/libData/AccountData/Transaction.h +++ b/src/libData/AccountData/Transaction.h @@ -119,6 +119,9 @@ class Transaction : public SerializableDataBlock { /// Returns the current version. const uint32_t& GetVersion() const; + /// Returns whether the current version is correct + bool VersionCorrect() const; + /// Returns the transaction nonce. const uint64_t& GetNonce() const; diff --git a/src/libEth/CMakeLists.txt b/src/libEth/CMakeLists.txt new file mode 100644 index 0000000000..51e296ad35 --- /dev/null +++ b/src/libEth/CMakeLists.txt @@ -0,0 +1,3 @@ +add_library(Eth Eth.cpp) +target_include_directories(Eth PUBLIC ${PROJECT_SOURCE_DIR}/src) +target_link_libraries(Eth EthCrypto) diff --git a/src/libEth/Eth.cpp b/src/libEth/Eth.cpp new file mode 100644 index 0000000000..785b6e68d4 --- /dev/null +++ b/src/libEth/Eth.cpp @@ -0,0 +1,150 @@ +/* + * Copyright (C) 2022 Zilliqa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "Eth.h" +#include "depends/common/RLP.h" +#include "jsonrpccpp/server.h" +#include "libUtils/DataConversion.h" + +using namespace jsonrpc; + +Json::Value populateReceiptHelper(std::string const& txnhash) { + Json::Value ret; + + ret["transactionHash"] = txnhash; + ret["blockHash"] = + "0x0000000000000000000000000000000000000000000000000000000000000000"; + ret["blockNumber"] = "0x429d3b"; + ret["contractAddress"] = nullptr; + ret["cumulativeGasUsed"] = "0x64b559"; + ret["from"] = "0x999"; // todo: fill + ret["gasUsed"] = "0xcaac"; + ret["logs"].append(Json::Value()); + ret["logsBloom"] = + "0x0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000"; + ret["root"] = + "0x0000000000000000000000000000000000000000000000000000000000001010"; + ret["status"] = nullptr; + ret["to"] = "0x888"; // todo: fill + ret["transactionIndex"] = "0x777"; // todo: fill + + return ret; +} + +Json::Value populateBlockHelper() { + Json::Value ret; + + ret["difficulty"] = "0x3ff800000"; + ret["extraData"] = "0x476574682f76312e302e302f6c696e75782f676f312e342e32"; + ret["gasLimit"] = "0x1388"; + ret["gasUsed"] = "0x0"; + ret["hash"] = + "0x88e96d4537bea4d9c05d12549907b32561d3bf31f45aae734cdc119f13406cb6"; + ret["logsBloom"] = + "0x00000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "0000000000000000000000000000000000000000000000000000000000000000000000" + "000000000000000000000000"; + ret["miner"] = "0x05a56e2d52c817161883f50c441c3228cfe54d9f"; + ret["mixHash"] = + "0x969b900de27b6ac6a67742365dd65f55a0526c41fd18e1b16f1a1215c2e66f59"; + ret["nonce"] = "0x539bd4979fef1ec4"; + ret["number"] = "0x1"; + ret["parentHash"] = + "0xd4e56740f876aef8c010b86a40d5f56745a118d0906a34e69aec8c0db1cb8fa3"; + ret["receiptsRoot"] = + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + ret["sha3Uncles"] = + "0x1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347"; + ret["size"] = "0x219"; + ret["stateRoot"] = + "0xd67e4d450343046425ae4271474353857ab860dbc0a1dde64b41b5cd3a532bf3"; + ret["timestamp"] = "0x55ba4224"; + ret["totalDifficulty"] = "0x7ff800000"; + ret["transactions"] = Json::arrayValue; + ret["transactionsRoot"] = + "0x56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421"; + ret["uncles"] = Json::arrayValue; + + return ret; +} + +// Given a RLP message, parse out the fields and return a EthFields object +EthFields parseRawTxFields(std::string const& message) { + EthFields ret; + + bytes asBytes; + DataConversion::HexStrToUint8Vec(message, asBytes); + + dev::RLP rlpStream1(asBytes); + int i = 0; + // todo: checks on size of rlp stream etc. + + ret.version = 65538; + + // RLP TX contains: nonce, gasPrice, gasLimit, to, value, data, v,r,s + for (auto it = rlpStream1.begin(); it != rlpStream1.end();) { + auto byteIt = (*it).operator bytes(); + + switch (i) { + case 0: + ret.nonce = uint32_t(*it); + break; + case 1: + ret.gasPrice = uint128_t(*it); + break; + case 2: + ret.gasLimit = uint64_t(*it); + break; + case 3: + ret.toAddr = byteIt; + break; + case 4: + ret.amount = uint128_t(*it); + break; + case 5: + ret.data = byteIt; + break; + case 6: // V - only needed for pub sig recovery + break; + case 7: // R + ret.signature.insert(ret.signature.end(), byteIt.begin(), byteIt.end()); + break; + case 8: // S + ret.signature.insert(ret.signature.end(), byteIt.begin(), byteIt.end()); + break; + default: + LOG_GENERAL(WARNING, "too many fields received in rlp!"); + } + + i++; + it++; + } + + return ret; +} diff --git a/src/libEth/Eth.h b/src/libEth/Eth.h new file mode 100644 index 0000000000..0f759a1eae --- /dev/null +++ b/src/libEth/Eth.h @@ -0,0 +1,42 @@ +/* + * Copyright (C) 2022 Zilliqa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef ZILLIQA_SRC_LIBETH_ETH_H_ +#define ZILLIQA_SRC_LIBETH_ETH_H_ + +#include +#include +#include "common/BaseType.h" + +struct EthFields { + uint32_t version{}; + uint64_t nonce{}; + bytes toAddr; + uint128_t amount; + uint128_t gasPrice; + uint64_t gasLimit{}; + bytes code; + bytes data; + bytes signature; +}; + +Json::Value populateReceiptHelper(std::string const& txnhash); +Json::Value populateBlockHelper(); + +EthFields parseRawTxFields(std::string const& message); + +#endif // ZILLIQA_SRC_LIBETH_ETH_H_ diff --git a/src/libNode/DSBlockProcessing.cpp b/src/libNode/DSBlockProcessing.cpp index 75f0552dc0..86c9c717c4 100644 --- a/src/libNode/DSBlockProcessing.cpp +++ b/src/libNode/DSBlockProcessing.cpp @@ -24,7 +24,6 @@ #include "common/Constants.h" #include "common/Messages.h" #include "common/Serializable.h" -#include "depends/common/RLP.h" #include "depends/libDatabase/MemoryDB.h" #include "depends/libTrie/TrieDB.h" #include "depends/libTrie/TrieHash.h" diff --git a/src/libServer/CMakeLists.txt b/src/libServer/CMakeLists.txt index 4a4fc03a6d..abd5c12d92 100644 --- a/src/libServer/CMakeLists.txt +++ b/src/libServer/CMakeLists.txt @@ -11,6 +11,7 @@ target_link_libraries (Server PUBLIC AccountData Validator RemoteStorageDB + Eth ${JSONCPP_LINK_TARGETS} ${WEBSOCKETPP_LIB} Utils @@ -20,4 +21,3 @@ target_link_libraries (Server PUBLIC target_link_libraries (Server PRIVATE CryptoUtils SafeServer) - diff --git a/src/libServer/IsolatedServer.cpp b/src/libServer/IsolatedServer.cpp index c92490d699..4809f57f03 100644 --- a/src/libServer/IsolatedServer.cpp +++ b/src/libServer/IsolatedServer.cpp @@ -247,6 +247,64 @@ IsolatedServer::IsolatedServer(Mediator& mediator, StartBlocknumIncrement(); } + + // Add the JSON-RPC eth style methods + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_blockNumber", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthBlockNumberI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("net_version", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetNetVersionI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_getBalance", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthBalanceI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_getBlockByNumber", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_BOOLEAN, NULL), + &LookupServer::GetEthBlockByNumberI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_gasPrice", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthGasPriceI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_getCode", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetCodeI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_estimateGas", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_OBJECT, + NULL), + &LookupServer::GetEthEstimateGasI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_getTransactionCount", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthTransactionCountI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_sendRawTransaction", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + NULL), + &IsolatedServer::GetEthSendRawTransactionI); + + AbstractServer::bindAndAddMethod( + jsonrpc::Procedure("eth_getTransactionReceipt", + jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, + "param01", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetTransactionReceiptI); } bool IsolatedServer::ValidateTxn(const Transaction& tx, const Address& fromAddr, @@ -373,8 +431,6 @@ Json::Value IsolatedServer::CreateTransaction(const Json::Value& _json) { throw JsonRpcException(RPC_INTERNAL_ERROR, "IsoServer is paused"); } - lock_guard g(m_blockMutex); - Transaction tx = JSONConversion::convertJsontoTx(_json); Json::Value ret; @@ -384,6 +440,8 @@ Json::Value IsolatedServer::CreateTransaction(const Json::Value& _json) { const Address fromAddr = tx.GetSenderAddr(); + lock_guard g(m_blockMutex); + { shared_lock lock( AccountStore::GetInstance().GetPrimaryMutex()); @@ -517,6 +575,166 @@ Json::Value IsolatedServer::CreateTransaction(const Json::Value& _json) { } } +Json::Value IsolatedServer::CreateTransactionEth(EthFields const& fields, + bytes const& pubKey) { + try { + if (m_pause) { + throw JsonRpcException(RPC_INTERNAL_ERROR, "IsoServer is paused"); + } + + Transaction tx{fields.version, + fields.nonce, + Address(fields.toAddr), + PubKey(pubKey, 0), + fields.amount, + fields.gasPrice, + fields.gasLimit, + bytes(), + fields.data, + Signature(fields.signature, 0)}; + + Json::Value ret; + + uint64_t senderNonce; + uint128_t senderBalance; + + const Address fromAddr = tx.GetSenderAddr(); + + lock_guard g(m_blockMutex); + + { + shared_lock lock( + AccountStore::GetInstance().GetPrimaryMutex()); + + const Account* sender = AccountStore::GetInstance().GetAccount(fromAddr); + + if (!ValidateTxn(tx, fromAddr, sender, m_gasPrice)) { + return ret; + } + + senderNonce = sender->GetNonce(); + senderBalance = sender->GetBalance(); + } + + if (senderNonce + 1 != tx.GetNonce()) { + throw JsonRpcException(RPC_INVALID_PARAMETER, + "Expected Nonce: " + to_string(senderNonce + 1)); + } + + if (senderBalance < tx.GetAmount()) { + throw JsonRpcException( + RPC_INVALID_PARAMETER, + "Insufficient Balance: " + senderBalance.str() + + " with an attempt to send: " + tx.GetAmount().str()); + } + + if (m_gasPrice > tx.GetGasPrice()) { + throw JsonRpcException(RPC_INVALID_PARAMETER, + "Minimum gas price greater: " + m_gasPrice.str()); + } + + switch (Transaction::GetTransactionType(tx)) { + case Transaction::ContractType::NON_CONTRACT: + break; + case Transaction::ContractType::CONTRACT_CREATION: + if (!ENABLE_SC) { + throw JsonRpcException(RPC_MISC_ERROR, "Smart contract is disabled"); + } + ret["ContractAddress"] = + Account::GetAddressForContract(fromAddr, senderNonce).hex(); + break; + case Transaction::ContractType::CONTRACT_CALL: { + if (!ENABLE_SC) { + throw JsonRpcException(RPC_MISC_ERROR, "Smart contract is disabled"); + } + + { + shared_lock lock( + AccountStore::GetInstance().GetPrimaryMutex()); + + const Account* account = + AccountStore::GetInstance().GetAccount(tx.GetToAddr()); + + if (account == nullptr) { + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, + "To addr is null"); + } + + else if (!account->isContract()) { + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, + "Non - contract address called"); + } + } + } break; + + case Transaction::ContractType::ERROR: + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, + "The code is empty and To addr is null"); + break; + default: + throw JsonRpcException(RPC_MISC_ERROR, "Txn type unexpected"); + } + + TransactionReceipt txreceipt; + + TxnStatus error_code; + bool throwError = false; + txreceipt.SetEpochNum(m_blocknum); + if (!AccountStore::GetInstance().UpdateAccountsTemp(m_blocknum, + 3, // Arbitrary values + true, tx, txreceipt, + error_code)) { + throwError = true; + } + LOG_GENERAL(INFO, "Processing On the isolated server "); + AccountStore::GetInstance().ProcessStorageRootUpdateBufferTemp(); + AccountStore::GetInstance().CleanNewLibrariesCacheTemp(); + + AccountStore::GetInstance().SerializeDelta(); + AccountStore::GetInstance().CommitTemp(); + + if (!m_timeDelta) { + AccountStore::GetInstance().InitTemp(); + } + + if (throwError) { + throw JsonRpcException(RPC_INVALID_PARAMETER, + "Error Code: " + to_string(error_code)); + } + + TransactionWithReceipt twr(tx, txreceipt); + + bytes twr_ser; + + twr.Serialize(twr_ser, 0); + + m_currEpochGas += txreceipt.GetCumGas(); + + if (!BlockStorage::GetBlockStorage().PutTxBody(m_blocknum, tx.GetTranID(), + twr_ser)) { + LOG_GENERAL(WARNING, "Unable to put tx body"); + } + const auto& txHash = tx.GetTranID(); + LookupServer::AddToRecentTransactions(txHash); + { + lock_guard g(m_txnBlockNumMapMutex); + m_txnBlockNumMap[m_blocknum].emplace_back(txHash); + } + LOG_GENERAL(INFO, "Added Txn " << txHash << " to blocknum: " << m_blocknum); + ret["TranID"] = txHash.hex(); + ret["Info"] = "Txn processed"; + WebsocketServer::GetInstance().ParseTxn(twr); + LOG_GENERAL(INFO, "Processing On the isolated server completed"); + return ret; + + } catch (const JsonRpcException& je) { + throw je; + } catch (exception& e) { + LOG_GENERAL(INFO, "[Error]" << e.what() << " Input: NA"); + throw JsonRpcException(RPC_MISC_ERROR, "Unable to Process"); + } +} + Json::Value IsolatedServer::GetTransactionsForTxBlock( const string& txBlockNum) { uint64_t txNum; diff --git a/src/libServer/IsolatedServer.h b/src/libServer/IsolatedServer.h index ef587fd525..f766797732 100644 --- a/src/libServer/IsolatedServer.h +++ b/src/libServer/IsolatedServer.h @@ -19,6 +19,7 @@ #define ZILLIQA_SRC_LIBSERVER_ISOLATEDSERVER_H_ #include "LookupServer.h" +#include "common/Constants.h" class Mediator; @@ -49,6 +50,23 @@ class IsolatedServer : public LookupServer, response = this->CreateTransaction(request[0u]); } + inline virtual void GetEthSendRawTransactionI(const Json::Value& request, + Json::Value& response) { + auto rawTx = request[0u].asString(); + + // Erase '0x' at the beginning if it exists + if (rawTx[1] == 'x') { + rawTx.erase(0, 2); + } + + auto const pubKey = RecoverECDSAPubSig(rawTx, ETH_CHAINID_INT); + + auto const fields = parseRawTxFields(rawTx); + auto const resp = CreateTransactionEth(fields, pubKey); + + response = resp["TranID"]; + } + inline virtual void IncreaseBlocknumI(const Json::Value& request, Json::Value& response) { response = this->IncreaseBlocknum(request[0u].asUInt()); @@ -89,6 +107,8 @@ class IsolatedServer : public LookupServer, std::string GetMinimumGasPrice(); std::string SetMinimumGasPrice(const std::string& gasPrice); Json::Value CreateTransaction(const Json::Value& _json); + Json::Value CreateTransactionEth(EthFields const& fields, + bytes const& pubKey); std::string IncreaseBlocknum(const uint32_t& delta); std::string GetBlocknum(); Json::Value GetTransactionsForTxBlock(const std::string& txBlockNum); diff --git a/src/libServer/LookupServer.cpp b/src/libServer/LookupServer.cpp index 48f52e84d8..c0a0718eb4 100644 --- a/src/libServer/LookupServer.cpp +++ b/src/libServer/LookupServer.cpp @@ -26,6 +26,7 @@ #include "libData/AccountData/Account.h" #include "libData/AccountData/AccountStore.h" #include "libData/AccountData/Transaction.h" +#include "libEth/Eth.h" #include "libMessage/Messenger.h" #include "libNetwork/Blacklist.h" #include "libNetwork/Guard.h" @@ -349,6 +350,7 @@ LookupServer::LookupServer(Mediator& mediator, jsonrpc::JSON_STRING, NULL), &LookupServer::GetStateProofI); + // Add Eth compatible RPC endpoints // todo: remove when all tests are updated to use eth_call this->bindAndAddMethod( jsonrpc::Procedure("GetEthCall", jsonrpc::PARAMS_BY_POSITION, @@ -362,6 +364,57 @@ LookupServer::LookupServer(Mediator& mediator, NULL), &LookupServer::GetEthCallI); + this->bindAndAddMethod( + jsonrpc::Procedure("eth_blockNumber", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthBlockNumberI); + + this->bindAndAddMethod( + jsonrpc::Procedure("net_version", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetNetVersionI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_getBalance", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthBalanceI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_getBlockByNumber", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_BOOLEAN, NULL), + &LookupServer::GetEthBlockByNumberI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_gasPrice", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthGasPriceI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_getCode", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetCodeI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_estimateGas", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_OBJECT, + NULL), + &LookupServer::GetEthEstimateGasI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_getTransactionCount", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + "param02", jsonrpc::JSON_STRING, NULL), + &LookupServer::GetEthTransactionCountI); + + this->bindAndAddMethod( + jsonrpc::Procedure("eth_sendRawTransaction", jsonrpc::PARAMS_BY_POSITION, + jsonrpc::JSON_STRING, "param01", jsonrpc::JSON_STRING, + NULL), + &LookupServer::GetEthSendRawTransactionI); + this->bindAndAddMethod( jsonrpc::Procedure("web3_clientVersion", jsonrpc::PARAMS_BY_POSITION, jsonrpc::JSON_STRING, NULL), @@ -565,10 +618,10 @@ bool ValidateTxn(const Transaction& tx, const Address& fromAddr, "CHAIN_ID incorrect"); } - if (DataConversion::UnpackB(tx.GetVersion()) != TRANSACTION_VERSION) { + if (!tx.VersionCorrect()) { throw JsonRpcException( ServerBase::RPC_VERIFY_REJECTED, - "Transaction version incorrect, Expected:" + + "Transaction version incorrect! Expected:" + to_string(TRANSACTION_VERSION) + " Actual:" + to_string(DataConversion::UnpackB(tx.GetVersion()))); } @@ -818,6 +871,197 @@ Json::Value LookupServer::CreateTransaction( } } +Json::Value LookupServer::CreateTransactionEth( + EthFields const& fields, bytes const& pubKey, const unsigned int num_shards, + const uint128_t& gasPrice, const CreateTransactionTargetFunc& targetFunc) { + LOG_MARKER(); + + if (!LOOKUP_NODE_MODE) { + throw JsonRpcException(RPC_INVALID_REQUEST, "Sent to a non-lookup"); + } + + if (Mediator::m_disableTxns) { + LOG_GENERAL(INFO, "Txns disabled - rejecting new txn"); + throw JsonRpcException(RPC_MISC_ERROR, "Unable to Process"); + } + + Transaction tx{fields.version, + fields.nonce, + Address(fields.toAddr), + PubKey(pubKey, 0), + fields.amount, + fields.gasPrice, + fields.gasLimit, + bytes(), + fields.data, + Signature(fields.signature, 0)}; + + try { + Json::Value ret; + + const Address fromAddr = tx.GetSenderAddr(); + + bool toAccountExist; + bool toAccountIsContract; + + { + shared_lock lock( + AccountStore::GetInstance().GetPrimaryMutex()); + + const Account* sender = + AccountStore::GetInstance().GetAccount(fromAddr, true); + const Account* toAccount = + AccountStore::GetInstance().GetAccount(tx.GetToAddr(), true); + + if (!ValidateTxn(tx, fromAddr, sender, gasPrice)) { + LOG_GENERAL(WARNING, "failed to validate TX!"); + return ret; + } + + toAccountExist = (toAccount != nullptr); + toAccountIsContract = toAccountExist && toAccount->isContract(); + } + + const unsigned int shard = Transaction::GetShardIndex(fromAddr, num_shards); + unsigned int mapIndex = shard; + switch (Transaction::GetTransactionType(tx)) { + case Transaction::ContractType::NON_CONTRACT: + if (ARCHIVAL_LOOKUP) { + mapIndex = SEND_TYPE::ARCHIVAL_SEND_SHARD; + } + if (toAccountExist) { + if (toAccountIsContract) { + throw JsonRpcException(ServerBase::RPC_INVALID_PARAMETER, + "Contract account won't accept normal txn"); + return false; + } + } + + ret["Info"] = "Non-contract txn, sent to shard"; + break; + case Transaction::ContractType::CONTRACT_CREATION: + if (!ENABLE_SC) { + throw JsonRpcException(RPC_MISC_ERROR, "Smart contract is disabled"); + } + if (ARCHIVAL_LOOKUP) { + mapIndex = SEND_TYPE::ARCHIVAL_SEND_SHARD; + } + ret["Info"] = "Contract Creation txn, sent to shard"; + ret["ContractAddress"] = + Account::GetAddressForContract(fromAddr, tx.GetNonce() - 1).hex(); + break; + case Transaction::ContractType::CONTRACT_CALL: { + if (!ENABLE_SC) { + throw JsonRpcException(RPC_MISC_ERROR, "Smart contract is disabled"); + } + + if (!toAccountExist) { + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, "To addr is null"); + } + + else if (!toAccountIsContract) { + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, + "Non - contract address called"); + } + + unsigned int to_shard = + Transaction::GetShardIndex(tx.GetToAddr(), num_shards); + // Use m_sendSCCallsToDS as initial setting + bool sendToDs = m_mediator.m_lookup->m_sendSCCallsToDS; + + // Todo: fill this part appropriately + + if ((to_shard == shard) && !sendToDs) { + if (tx.GetGasLimit() > SHARD_MICROBLOCK_GAS_LIMIT) { + throw JsonRpcException( + RPC_INVALID_PARAMETER, + "txn gas limit exceeding shard maximum limit"); + } + if (ARCHIVAL_LOOKUP) { + mapIndex = SEND_TYPE::ARCHIVAL_SEND_SHARD; + } + ret["Info"] = + "Contract Txn, Shards Match of the sender " + "and receiver"; + } else { + if (tx.GetGasLimit() > DS_MICROBLOCK_GAS_LIMIT) { + throw JsonRpcException(RPC_INVALID_PARAMETER, + "txn gas limit exceeding ds maximum limit"); + } + if (ARCHIVAL_LOOKUP) { + mapIndex = SEND_TYPE::ARCHIVAL_SEND_DS; + } else { + mapIndex = num_shards; + } + ret["Info"] = "Contract Txn, Sent To Ds"; + } + } break; + case Transaction::ContractType::ERROR: + throw JsonRpcException(RPC_INVALID_ADDRESS_OR_KEY, + "Code is empty and To addr is null"); + break; + default: + throw JsonRpcException(RPC_MISC_ERROR, "Txn type unexpected"); + } + if (m_mediator.m_lookup->m_sendAllToDS) { + if (ARCHIVAL_LOOKUP) { + mapIndex = SEND_TYPE::ARCHIVAL_SEND_DS; + } else { + mapIndex = num_shards; + } + } + if (!targetFunc(tx, mapIndex)) { + throw JsonRpcException(RPC_DATABASE_ERROR, + "Txn could not be added as database exceeded " + "limit or the txn was already present"); + } + ret["TranID"] = tx.GetTranID().hex(); + return ret; + } catch (const JsonRpcException& je) { + throw je; + } catch (exception& e) { + LOG_GENERAL(INFO, "[Error]" << e.what() << " Input: N/A"); + throw JsonRpcException(RPC_MISC_ERROR, "Unable to Process"); + } +} + +Json::Value LookupServer::GetEthBlockByNumber() { + return populateBlockHelper(); +} + +Json::Value LookupServer::GetTransactionReceipt(const std::string& txnhash) { + Json::Value ret; + + try { + if (!REMOTESTORAGE_DB_ENABLE) { + throw JsonRpcException(RPC_DATABASE_ERROR, "API not supported"); + } + if (txnhash.size() != TRAN_HASH_SIZE * 2) { + throw JsonRpcException(RPC_INVALID_PARAMETER, + "Txn Hash size not appropriate"); + } + + const auto& result = RemoteStorageDB::GetInstance().QueryTxnHash(txnhash); + + if (result.isMember("error")) { + throw JsonRpcException(RPC_DATABASE_ERROR, "Internal database error"); + } else if (result == Json::Value::null) { + // No txnhash matches the one in DB + return ret; + } + + ret = populateReceiptHelper(txnhash); + + return ret; + } catch (const JsonRpcException& je) { + throw je; + } catch (exception& e) { + LOG_GENERAL(WARNING, "[Error]" << e.what()); + throw JsonRpcException(RPC_MISC_ERROR, + string("Unable To Process: ") + e.what()); + } +} + Json::Value LookupServer::GetTransaction(const string& transactionHash) { LOG_MARKER(); @@ -1078,6 +1322,36 @@ string LookupServer::GetEthCall(const Json::Value& _json) { return result; } +// Get balance, but return the result as hex rather than decimal string +Json::Value LookupServer::GetBalance(const string& address, bool noThrow) { + try { + auto ret = this->GetBalance(address); + + // Will fit into 128 since that is the native zil balance + // size + uint128_t balance{ret["balance"].asString()}; + + // Convert the result from decimal string to hex string + std::stringstream ss; + ss << std::hex << balance; // int decimal_value + std::string res(ss.str()); + + ret["balance"] = res; + + return ret; + } catch (exception& e) { + LOG_GENERAL(INFO, "[Error]" << e.what() << " Input: " << address); + if (noThrow) { + Json::Value ret; + ret["balance"] = "0x0"; + ret["nonce"] = static_cast(0); + return ret; + } else { + throw JsonRpcException(RPC_MISC_ERROR, "Unable To Process"); + } + } +} + std::string LookupServer::GetWeb3ClientVersion() { LOG_MARKER(); return "to do implement web3 version string"; diff --git a/src/libServer/LookupServer.h b/src/libServer/LookupServer.h index 057d98c61b..5b43148fce 100644 --- a/src/libServer/LookupServer.h +++ b/src/libServer/LookupServer.h @@ -19,6 +19,10 @@ #define ZILLIQA_SRC_LIBSERVER_LOOKUPSERVER_H_ #include "Server.h" +#include "common/Constants.h" +#include "libCrypto/EthCrypto.h" +#include "libEth/Eth.h" +#include "libUtils/Logger.h" class Mediator; @@ -302,6 +306,100 @@ class LookupServer : public Server, response = this->GetEthCall(request[0u]); } + // Eth style functions here + inline virtual void GetEthBlockNumberI(const Json::Value& request, + Json::Value& response) { + (void)request; + static uint64_t block_number = 2675001; + block_number++; + + std::stringstream stream; + stream << "0x" << std::hex << block_number; + + response = stream.str(); + } + + inline virtual void GetEthBlockByNumberI(const Json::Value& request, + Json::Value& response) { + (void)request; + response = this->GetEthBlockByNumber(); + } + + inline virtual void GetEthGasPriceI(const Json::Value& request, + Json::Value& response) { + (void)request; + response = "0xd9e63a68c"; + } + + inline virtual void GetCodeI(const Json::Value& request, + Json::Value& response) { + (void)request; + response = "0x"; + } + + inline virtual void GetEthEstimateGasI(const Json::Value& request, + Json::Value& response) { + (void)request; + response = "0x5208"; + } + + inline virtual void GetEthTransactionCountI(const Json::Value& request, + Json::Value& response) { + (void)request; + + std::string address = request[0u].asString(); + DataConversion::NormalizeHexString(address); + int resp = 0; + + resp = this->GetBalance(address)["nonce"].asUInt() + 1; + + response = DataConversion::IntToHexString(resp); + } + + inline virtual void GetTransactionReceiptI(const Json::Value& request, + Json::Value& response) { + (void)request; + + response = this->GetTransactionReceipt(request[0u].asString()); + } + + inline virtual void GetEthSendRawTransactionI(const Json::Value& request, + Json::Value& response) { + (void)request; + auto rawTx = request[0u].asString(); + + // Erase '0x' at the beginning if it exists + if (rawTx[1] == 'x') { + rawTx.erase(0, 2); + } + + auto pubKey = RecoverECDSAPubSig(rawTx, stoi(ETH_CHAINID)); + auto fields = parseRawTxFields(rawTx); + auto shards = m_mediator.m_lookup->GetShardPeers().size(); + auto currentGasPrice = + m_mediator.m_dsBlockChain.GetLastBlock().GetHeader().GetGasPrice(); + + auto resp = CreateTransactionEth(fields, pubKey, shards, currentGasPrice, + m_createTransactionTarget); + + response = resp["TranID"]; + } + + inline virtual void GetEthBalanceI(const Json::Value& request, + Json::Value& response) { + (void)request; + std::string address = request[0u].asString(); + DataConversion::NormalizeHexString(address); + + auto resp = this->GetBalance(address, true)["balance"]; + + auto balanceStr = resp.asString(); + + resp = balanceStr; + + response = resp; + } + /** * @brief Handles json rpc 2.0 request on method: web3_clientVersion * @param request : Params none @@ -472,6 +570,7 @@ class LookupServer : public Server, Json::Value GetLatestDsBlock(); Json::Value GetLatestTxBlock(); Json::Value GetBalance(const std::string& address); + Json::Value GetBalance(const std::string& address, bool noThrow); std::string GetMinimumGasPrice(); Json::Value GetSmartContracts(const std::string& address); std::string GetContractAddressFromTransactionID(const std::string& tranID); @@ -508,6 +607,14 @@ class LookupServer : public Server, std::string GetNumTxnsDSEpoch(); std::string GetNumTxnsTxEpoch(); + // Eth calls + Json::Value GetTransactionReceipt(const std::string& txnhash); + Json::Value GetEthBlockByNumber(); + Json::Value CreateTransactionEth( + EthFields const& fields, bytes const& pubKey, + const unsigned int num_shards, const uint128_t& gasPrice, + const CreateTransactionTargetFunc& targetFunc); + size_t GetNumTransactions(uint64_t blockNum); bool StartCollectorThread(); std::string GetNodeState(); diff --git a/src/libUtils/DataConversion.cpp b/src/libUtils/DataConversion.cpp index 4be72be0c5..65773e0e06 100644 --- a/src/libUtils/DataConversion.cpp +++ b/src/libUtils/DataConversion.cpp @@ -19,20 +19,21 @@ using namespace std; -bool DataConversion::HexStringToUint64(const std::string& s, uint64_t* res) { +bool DataConversion::HexStrToUint8Vec(const string& hex_input, bytes& out) { try { - *res = std::stoull(s, nullptr, 16); - } catch (const std::invalid_argument& e) { - LOG_GENERAL(WARNING, "Convert failed, invalid input: " << s); - return false; - } catch (const std::out_of_range& e) { - LOG_GENERAL(WARNING, "Convert failed, out of range: " << s); + out.clear(); + boost::algorithm::unhex(hex_input.begin(), hex_input.end(), + back_inserter(out)); + } catch (exception& e) { + LOG_GENERAL(WARNING, "Failed HexStrToUint8Vec conversion with exception: " + << e.what()); return false; } return true; } -bool DataConversion::HexStrToUint8Vec(const string& hex_input, bytes& out) { +bytes DataConversion::HexStrToUint8VecRet(const string& hex_input) { + bytes out; try { out.clear(); boost::algorithm::unhex(hex_input.begin(), hex_input.end(), @@ -40,9 +41,9 @@ bool DataConversion::HexStrToUint8Vec(const string& hex_input, bytes& out) { } catch (exception& e) { LOG_GENERAL(WARNING, "Failed HexStrToUint8Vec conversion with exception: " << e.what()); - return false; + return out; } - return true; + return out; } bool DataConversion::HexStrToStdArray(const string& hex_input, @@ -80,6 +81,18 @@ bool DataConversion::Uint8VecToHexStr(const bytes& hex_vec, string& str) { return true; } +std::string DataConversion::Uint8VecToHexStrRet(const bytes& hex_vec) { + std::string str; + try { + str = ""; + boost::algorithm::hex(hex_vec.begin(), hex_vec.end(), back_inserter(str)); + } catch (exception& e) { + LOG_GENERAL(WARNING, "Failed Uint8VecToHexStr conversion"); + return str; + } + return str; +} + bool DataConversion::StringToHexStr(const string& hex_str, string& str) { try { str = ""; diff --git a/src/libUtils/DataConversion.h b/src/libUtils/DataConversion.h index cec2a3d429..b54eed85bb 100644 --- a/src/libUtils/DataConversion.h +++ b/src/libUtils/DataConversion.h @@ -34,11 +34,36 @@ class DataConversion { public: /// Converts alphanumeric hex string to Uint64. - static bool HexStringToUint64(const std::string& s, uint64_t* res); + static bool HexStringToUint64(const std::string& s, uint64_t* res) { + try { + *res = std::stoull(s, nullptr, 16); + } catch (const std::invalid_argument& e) { + LOG_GENERAL(WARNING, "Convert failed, invalid input: " << s); + return false; + } catch (const std::out_of_range& e) { + LOG_GENERAL(WARNING, "Convert failed, out of range: " << s); + return false; + } + return true; + } + + static uint64_t HexStringToUint64Ret(const std::string& s) { + uint64_t ret = 0; + + if (s.size() > 2 && s[1] == 'x') { + HexStringToUint64(std::string(s.c_str() + 2), &ret); + } else { + HexStringToUint64(s, &ret); + } + + return ret; + } /// Converts alphanumeric hex string to byte vector. static bool HexStrToUint8Vec(const std::string& hex_input, bytes& out); + static bytes HexStrToUint8VecRet(const std::string& hex_input); + /// Converts alphanumeric hex string to 32-byte array. static bool HexStrToStdArray(const std::string& hex_input, std::array& d); @@ -53,6 +78,8 @@ class DataConversion { /// Converts byte vector to alphanumeric hex string. static bool Uint8VecToHexStr(const bytes& hex_vec, std::string& str); + static std::string Uint8VecToHexStrRet(const bytes& hex_vec); + /// Converts byte vector to alphanumeric hex string. static bool Uint8VecToHexStr(const bytes& hex_vec, unsigned int offset, unsigned int len, std::string& str); @@ -133,6 +160,19 @@ class DataConversion { auto lower = x & 0x0F; return upper ? clz_lookup[upper] : 4 + clz_lookup[lower]; } + + template + static std::string IntToHexString(T number, bool withX = true) { + std::stringstream stream; + + if (withX) { + stream << "0x" << std::hex << (int)number; + } else { + stream << std::hex << (int)number; + } + + return stream.str(); + } }; #endif // ZILLIQA_SRC_LIBUTILS_DATACONVERSION_H_ diff --git a/src/libValidator/Validator.cpp b/src/libValidator/Validator.cpp index e5cee36534..7c9788a139 100644 --- a/src/libValidator/Validator.cpp +++ b/src/libValidator/Validator.cpp @@ -63,11 +63,7 @@ bool Validator::CheckCreatedTransaction(const Transaction& tx, return false; } - LOG_GENERAL(WARNING, "Transaction version incorrect " - << "Expected:" << TRANSACTION_VERSION << " Actual:" - << DataConversion::UnpackB(tx.GetVersion())); - - if (DataConversion::UnpackB(tx.GetVersion()) != TRANSACTION_VERSION) { + if (!tx.VersionCorrect()) { LOG_GENERAL(WARNING, "Transaction version incorrect " << "Expected:" << TRANSACTION_VERSION << " Actual:" << DataConversion::UnpackB(tx.GetVersion())); @@ -135,7 +131,7 @@ bool Validator::CheckCreatedTransactionFromLookup(const Transaction& tx, return false; } - if (DataConversion::UnpackB(tx.GetVersion()) != TRANSACTION_VERSION) { + if (!tx.VersionCorrect()) { LOG_GENERAL(WARNING, "Transaction version incorrect " << "Expected:" << TRANSACTION_VERSION << " Actual:" << DataConversion::UnpackB(tx.GetVersion())); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 864ef6def8..854cf189d5 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -4,6 +4,7 @@ add_subdirectory (cmd) add_subdirectory (Data) add_subdirectory (Directory) add_subdirectory (depends) +add_subdirectory (Crypto) add_subdirectory (Incentives) add_subdirectory (libTestUtils) add_subdirectory (Lookup) diff --git a/tests/Crypto/CMakeLists.txt b/tests/Crypto/CMakeLists.txt index 62b8bb428c..1baaf69d35 100644 --- a/tests/Crypto/CMakeLists.txt +++ b/tests/Crypto/CMakeLists.txt @@ -5,6 +5,11 @@ add_executable(Test_Sha2 Test_Sha2.cpp) target_link_libraries(Test_Sha2 PUBLIC crypto Utils Boost::unit_test_framework) add_test(NAME Test_Sha2 COMMAND Test_Sha2) +add_executable(Test_EthCrypto Test_EthCrypto.cpp) +target_link_libraries(Test_EthCrypto PUBLIC crypto Utils Boost::unit_test_framework) +target_link_libraries(Test_EthCrypto PUBLIC EthCrypto Eth OpenSSL::Crypto Common jsonrpc::client) +add_test(NAME Test_EthCrypto COMMAND Test_EthCrypto) + #add_executable(Test_Schnorr Test_Schnorr.cpp) #target_link_libraries(Test_Schnorr PUBLIC Crypto) #add_test(NAME Test_Schnorr COMMAND Test_Schnorr) @@ -12,3 +17,4 @@ add_test(NAME Test_Sha2 COMMAND Test_Sha2) #add_executable(Test_MultiSig Test_MultiSig.cpp) #target_link_libraries(Test_MultiSig PUBLIC Crypto) #add_test(NAME Test_MultiSig COMMAND Test_MultiSig) + diff --git a/tests/Crypto/Test_EthCrypto.cpp b/tests/Crypto/Test_EthCrypto.cpp new file mode 100644 index 0000000000..91540d598d --- /dev/null +++ b/tests/Crypto/Test_EthCrypto.cpp @@ -0,0 +1,92 @@ +/* + * Copyright (C) 2019 Zilliqa + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + * + * Test cases obtained from https://www.di-mgt.com.au/sha_testvectors.html + */ + +#include "libCrypto/EthCrypto.h" +#include "libEth/Eth.h" +#include "libUtils/DataConversion.h" + +#define BOOST_TEST_MODULE ethCryptotest +#define BOOST_TEST_DYN_LINK +#include + +using namespace std; + +/// Just an alloca "wrapper" to silence uint64_t to size_t conversion warnings +/// in windows consider replacing alloca calls with something better though! +#define our_alloca(param__) alloca((size_t)(param__)) + +BOOST_AUTO_TEST_SUITE(ethCryptotest) + +/** + * \brief Test parsing of raw TX fields + * + * \details Test the fields of a raw TX can be parsed correctly + */ +BOOST_AUTO_TEST_CASE(TestEthTXParse) { + // Parse a RLP Tx to extract the fields + std::string const rlp = + "f86e01850d9e63a68c82520894673e5ef1ae0a2ef7d0714a96a734ffcd1d8a381f872386" + "f26fc1000080830102bda0ef23fef2ffa3538b2c8204278ad0427491b5359c346c50a923" + "6b9b554c45749ea02da3eba55c891dde91e73a312fd3748936fb7af8fb34c2f0fed8a987" + "7f227e1d"; + + auto const result = parseRawTxFields(rlp); + + BOOST_CHECK_EQUAL(DataConversion::Uint8VecToHexStrRet(result.toAddr), + "673E5EF1AE0A2EF7D0714A96A734FFCD1D8A381F"); + BOOST_CHECK_EQUAL(result.amount, uint128_t{"10000000000000000"}); + BOOST_CHECK_EQUAL(result.gasPrice, uint128_t{"58491905676"}); + BOOST_CHECK_EQUAL(result.code.size(), 0); + BOOST_CHECK_EQUAL(result.data.size(), 0); + BOOST_CHECK_EQUAL( + DataConversion::Uint8VecToHexStrRet(result.signature), + "EF23FEF2FFA3538B2C8204278AD0427491B5359C346C50A9236B9B554C45749E2DA3EBA5" + "5C891DDE91E73A312FD3748936FB7AF8FB34C2F0FED8A9877F227E1D"); +} + +/** + * \brief Test recovery of ECDSA pub key given only message and signature + * + * \details As above + */ +BOOST_AUTO_TEST_CASE(TestRecoverECDSASig) { + // Example RLP raw transaction, sent from metamask with chain ID 33101 + // Note: must use that chain id for this RLP example + // private key: + // a8b68f4800bc7513fca14a752324e41b2fa0a7c06e80603aac9e5961e757d906 eth addr: + // 0x6cCAa29b6cD36C8238E8Fa137311de6153b0b4e7 seed phrase: art rubber roof off + // fetch bulb board foot payment engage pyramid tiger + + std::string const rlp = + "f86e01850d9e63a68c82520894673e5ef1ae0a2ef7d0714a96a734ffcd1d8a381f872386" + "f26fc1000080830102bda0ef23fef2ffa3538b2c8204278ad0427491b5359c346c50a923" + "6b9b554c45749ea02da3eba55c891dde91e73a312fd3748936fb7af8fb34c2f0fed8a987" + "7f227e1d"; + std::string const pubKey = + "041419977507436A81DD0AC7BEB6C7C0DECCBF1A1A1A5E595F647892628A0F65BC9D19CB" + "F0712F881B529D39E7F75D543DC3E646880A0957F6E6DF5C1B5D0EB278"; + + auto const result = RecoverECDSAPubSig(rlp, 33101); + auto const restultStr = DataConversion::Uint8VecToHexStrRet(result); + + // If this fails, check the pubkey starts with '04' (is uncompressed) + BOOST_CHECK_EQUAL(restultStr.compare(pubKey), 0); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/EvmLookupServer/Test_EvmLookupServer.cpp b/tests/EvmLookupServer/Test_EvmLookupServer.cpp index 8b4f3f978a..f1e1ebbdc4 100644 --- a/tests/EvmLookupServer/Test_EvmLookupServer.cpp +++ b/tests/EvmLookupServer/Test_EvmLookupServer.cpp @@ -517,4 +517,209 @@ BOOST_AUTO_TEST_CASE(test_eth_get_uncle_count_by_number) { BOOST_CHECK_EQUAL(response, expectedResponse); } +BOOST_AUTO_TEST_CASE(test_eth_blockNumber) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + + lookupServer.GetEthBlockNumberI(paramsRequest, response); + + if (!(response.asString()[0] == '0' && response.asString()[1] == 'x')) { + BOOST_FAIL("Failed to get block number!"); + } +} + +BOOST_AUTO_TEST_CASE(test_eth_net_version) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + + lookupServer.GetNetVersionI(paramsRequest, response); + + if (response.asString().size() > 0) { + BOOST_FAIL("Failed to get net version"); + } +} + +BOOST_AUTO_TEST_CASE(test_eth_get_balance) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + paramsRequest[0u] = "0x6cCAa29b6cD36C8238E8Fa137311de6153b0b4e7"; + + lookupServer.GetEthBalanceI(paramsRequest, response); + + if (!(response.asString() == "0x0")) { + BOOST_FAIL("Failed to get empty balance!"); + } +} + +BOOST_AUTO_TEST_CASE(test_eth_get_block_by_nummber) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + paramsRequest[0u] = "0x0"; + + lookupServer.GetEthBlockByNumberI(paramsRequest, response); + + // Todo: proper checks for block structure +} + +BOOST_AUTO_TEST_CASE(test_eth_get_gas_price) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + + lookupServer.GetEthGasPriceI(paramsRequest, response); + + if (response.asString()[0] != '0') { + BOOST_FAIL("Failed to get gas price"); + } +} + +BOOST_AUTO_TEST_CASE(test_eth_estimate_gas) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + + lookupServer.GetEthEstimateGasI(paramsRequest, response); + + if (response.asString()[0] != '0') { + BOOST_FAIL("Failed to get gas price"); + } +} + +BOOST_AUTO_TEST_CASE(test_eth_get_transaction_count) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + Address accountAddress{"a744160c3De133495aB9F9D77EA54b325b045670"}; + Account account; + if (!AccountStore::GetInstance().IsAccountExist(accountAddress)) { + AccountStore::GetInstance().AddAccount(accountAddress, account); + } + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + Json::Value paramsRequest = Json::Value(Json::arrayValue); + paramsRequest[0u] = "0xa744160c3De133495aB9F9D77EA54b325b045670"; + + lookupServer.GetEthTransactionCountI(paramsRequest, response); + + // 0x response + if (response.asString()[0] != '0') { + BOOST_FAIL("Failed to get TX count"); + } +} + +/* +BOOST_AUTO_TEST_CASE(test_eth_send_raw_transaction) { + INIT_STDOUT_LOGGER(); + + LOG_MARKER(); + + EvmClient::GetInstance([]() { return std::make_shared(); }); + + PairOfKey pairOfKey = Schnorr::GenKeyPair(); + Peer peer; + Mediator mediator(pairOfKey, peer); + AbstractServerConnectorMock abstractServerConnector; + + LookupServer lookupServer(mediator, abstractServerConnector); + Json::Value response; + // call the method on the lookup server with params + + Json::Value paramsRequest = Json::Value(Json::arrayValue); + paramsRequest[0u] = +"f86e80850d9e63a68c82520894673e5ef1ae0a2ef7d0714a96a734ffcd1d8a381f881bc16d674ec8000080820cefa04728e87b280814295371adf0b7ccc3ec802a45bd31d13668b5ab51754c110f8ea02d0450641390c9ed56fcbbc64dcb5b07f7aece78739ef647f10cc93d4ecaa496"; + + lookupServer.GetEthSendRawTransactionI(paramsRequest, response); + + const Json::Value expectedResponse = Json::arrayValue; + BOOST_CHECK_EQUAL(response, expectedResponse); +} +*/ + BOOST_AUTO_TEST_SUITE_END()