diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index cda41654..ca0c83a6 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -9,6 +9,8 @@ stages: - build - test - dockerize + - python-test + - deploy build-mainnet: stage: build @@ -44,12 +46,11 @@ test-mainnet: dockerize-mainnet: stage: dockerize - dependencies: - - test-mainnet variables: IMAGE: $CI_REGISTRY_IMAGE/mainnet/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA before_script: - docker info + - docker builder prune -a -f - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY script: - docker build --no-cache -t $IMAGE . @@ -58,8 +59,6 @@ dockerize-mainnet: - docker rmi $IMAGE tags: - builder - when: - manual timeout: 3h @@ -80,12 +79,27 @@ build-testnet: - build/libraries/ - build/programs/ - build/tests/ + when: manual tags: - builder - when: - manual - timeout: - 3h + +deploy-testnet: + stage: deploy + dependencies: + - build-testnet + script: + - sudo systemctl stop witness + - rm $WORK_DIR/peerplays/witness_node || true + - cp build/programs/witness_node/witness_node $WORK_DIR/peerplays/ + - sudo systemctl restart witness + rules: + - if: $CI_COMMIT_BRANCH == "master" + when: always + environment: + name: devnet + url: $DEVNET_URL + tags: + - devnet test-testnet: stage: test @@ -105,8 +119,6 @@ test-testnet: dockerize-testnet: stage: dockerize - dependencies: - - test-testnet variables: IMAGE: $CI_REGISTRY_IMAGE/testnet/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA before_script: @@ -123,3 +135,37 @@ dockerize-testnet: manual timeout: 3h + +test-e2e: + stage: python-test + variables: + IMAGE: $CI_REGISTRY_IMAGE/mainnet/$CI_COMMIT_REF_SLUG:$CI_COMMIT_SHA + before_script: + - docker info + - docker login -u "$CI_REGISTRY_USER" -p "$CI_REGISTRY_PASSWORD" $CI_REGISTRY + script: + - git clone https://gitlab.com/PBSA/tools-libs/peerplays-utils.git + - cd peerplays-utils/peerplays-qa-environment + - git checkout origin/feature/python-e2e-tests-for-CI + - cd e2e-tests/ + - python3 -m venv venv + - source venv/bin/activate + - pip3 install -r requirements.txt + - docker-compose down --remove-orphans + - docker ps -a + - docker pull $IMAGE + - docker tag $IMAGE peerplays-base:latest + - docker image ls -a + - docker-compose build + - python3 main.py --start all + - docker ps -a + - python3 -m pytest test_btc_init_state.py test_hive_inital_state.py test_pp_inital_state.py + - python3 main.py --stop + - deactivate + - docker ps -a + after_script: + - docker rmi $(docker images -a | grep -v 'hive-for-peerplays\|ethereum-for-peerplays\|bitcoin-for-peerplays\|ubuntu-for-peerplays' | awk '{print $3}') + tags: + - python-tests + when: + manual diff --git a/.gitmodules b/.gitmodules index e535465c..d9c387a6 100644 --- a/.gitmodules +++ b/.gitmodules @@ -5,5 +5,5 @@ [submodule "libraries/fc"] path = libraries/fc url = https://gitlab.com/PBSA/tools-libs/peerplays-fc.git - branch = latest-fc + branch = develop ignore = dirty diff --git a/Dockerfile b/Dockerfile index 7a76c136..b9068197 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ FROM ubuntu:20.04 -MAINTAINER Peerplays Blockchain Standards Association #=============================================================================== # Ubuntu setup @@ -11,15 +10,14 @@ RUN \ apt-utils \ autoconf \ bash \ + bison \ build-essential \ ca-certificates \ - cmake \ dnsutils \ - doxygen \ expect \ + flex \ git \ graphviz \ - libboost1.67-all-dev \ libbz2-dev \ libcurl4-openssl-dev \ libncurses-dev \ @@ -35,7 +33,6 @@ RUN \ ntp \ openssh-server \ pkg-config \ - perl \ python3 \ python3-jinja2 \ sudo \ @@ -53,16 +50,105 @@ RUN echo 'peerplays:peerplays' | chpasswd # SSH EXPOSE 22 +WORKDIR /home/peerplays/src + +#=============================================================================== +# Boost setup +#=============================================================================== + +RUN \ + wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \ + tar -xzf boost_1_72_0.tar.gz && \ + cd boost_1_72_0 && \ + ./bootstrap.sh && \ + ./b2 install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# cmake setup +#=============================================================================== + +RUN \ + wget https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2-linux-x86_64.sh && \ + chmod 755 ./cmake-3.24.2-linux-x86_64.sh && \ + ./cmake-3.24.2-linux-x86_64.sh --prefix=/usr --skip-license && \ + cmake --version && \ + rm -rf /home/peerplays/src/* + #=============================================================================== # libzmq setup #=============================================================================== -WORKDIR /home/peerplays/ +RUN \ + wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.tar.gz && \ + tar -xzvf v4.3.4.tar.gz && \ + cd libzmq-4.3.4 && \ + mkdir build && \ + cd build && \ + cmake .. && \ + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# cppzmq setup +#=============================================================================== RUN \ - wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.zip && \ - unzip v4.3.4.zip && \ - cd libzmq-4.3.4 && \ + wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.9.0.tar.gz && \ + tar -xzvf v4.9.0.tar.gz && \ + cd cppzmq-4.9.0 && \ + mkdir build && \ + cd build && \ + cmake .. && \ + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# gsl setup +#=============================================================================== + +RUN \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libpcre3-dev + +RUN \ + wget https://github.com/imatix/gsl/archive/refs/tags/v4.1.4.tar.gz && \ + tar -xzvf v4.1.4.tar.gz && \ + cd gsl-4.1.4 && \ + make -j$(nproc) && \ + make install && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# libbitcoin-build setup +# libbitcoin-explorer setup +#=============================================================================== + +RUN \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libsodium-dev + +RUN \ + git clone --branch version3.8.0 --depth 1 https://gitlab.com/PBSA/peerplays-1.0/libbitcoin-explorer.git && \ + cd libbitcoin-explorer && \ + ./install.sh && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# Doxygen setup +#=============================================================================== + +RUN \ + sudo apt install -y bison flex && \ + wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \ + tar -xvf Release_1_8_17.tar.gz && \ + cd doxygen-Release_1_8_17 && \ mkdir build && \ cd build && \ cmake .. && \ @@ -70,18 +156,14 @@ RUN \ ldconfig #=============================================================================== -# cppzmq setup +# Perl setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ - wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.8.1.zip && \ - unzip v4.8.1.zip && \ - cd cppzmq-4.8.1 && \ - mkdir build && \ - cd build && \ - cmake .. && \ + wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \ + tar -xvf v5.30.0.tar.gz && \ + cd perl5-5.30.0 && \ + ./Configure -des && \ make -j$(nproc) install && \ ldconfig @@ -89,8 +171,6 @@ RUN \ # Peerplays setup #=============================================================================== -WORKDIR /home/peerplays/ - ## Clone Peerplays #RUN \ # git clone https://gitlab.com/PBSA/peerplays.git && \ @@ -106,6 +186,8 @@ ADD . peerplays # Configure Peerplays RUN \ cd peerplays && \ + git submodule update --init --recursive && \ + git log --oneline -n 5 && \ mkdir build && \ cd build && \ cmake -DCMAKE_BUILD_TYPE=Release .. @@ -119,8 +201,8 @@ WORKDIR /home/peerplays/peerplays-network # Setup Peerplays runimage RUN \ - ln -s /home/peerplays/peerplays/build/programs/cli_wallet/cli_wallet ./ && \ - ln -s /home/peerplays/peerplays/build/programs/witness_node/witness_node ./ + ln -s /home/peerplays/src/peerplays/build/programs/cli_wallet/cli_wallet ./ && \ + ln -s /home/peerplays/src/peerplays/build/programs/witness_node/witness_node ./ RUN ./witness_node --create-genesis-json genesis.json && \ rm genesis.json diff --git a/Dockerfile.18.04 b/Dockerfile.18.04 index d3fa1d75..1fe40a55 100644 --- a/Dockerfile.18.04 +++ b/Dockerfile.18.04 @@ -1,5 +1,4 @@ FROM ubuntu:18.04 -MAINTAINER Peerplays Blockchain Standards Association #=============================================================================== # Ubuntu setup @@ -11,11 +10,12 @@ RUN \ apt-utils \ autoconf \ bash \ + bison \ build-essential \ ca-certificates \ dnsutils \ - doxygen \ expect \ + flex \ git \ graphviz \ libbz2-dev \ @@ -33,7 +33,6 @@ RUN \ ntp \ openssh-server \ pkg-config \ - perl \ python3 \ python3-jinja2 \ sudo \ @@ -51,41 +50,105 @@ RUN echo 'peerplays:peerplays' | chpasswd # SSH EXPOSE 22 +WORKDIR /home/peerplays/src + #=============================================================================== # Boost setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ - wget -c 'http://sourceforge.net/projects/boost/files/boost/1.67.0/boost_1_67_0.tar.bz2/download' -O boost_1_67_0.tar.bz2 && \ - tar xjf boost_1_67_0.tar.bz2 && \ - cd boost_1_67_0/ && \ + wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \ + tar -xzf boost_1_72_0.tar.gz && \ + cd boost_1_72_0 && \ ./bootstrap.sh && \ - ./b2 install + ./b2 install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* #=============================================================================== # cmake setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ - wget -c 'https://cmake.org/files/v3.23/cmake-3.23.1-linux-x86_64.sh' -O cmake-3.23.1-linux-x86_64.sh && \ - chmod 755 ./cmake-3.23.1-linux-x86_64.sh && \ - ./cmake-3.23.1-linux-x86_64.sh --prefix=/usr/ --skip-license && \ - cmake --version + wget https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2-linux-x86_64.sh && \ + chmod 755 ./cmake-3.24.2-linux-x86_64.sh && \ + ./cmake-3.24.2-linux-x86_64.sh --prefix=/usr --skip-license && \ + cmake --version && \ + rm -rf /home/peerplays/src/* #=============================================================================== # libzmq setup #=============================================================================== -WORKDIR /home/peerplays/ +RUN \ + wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.tar.gz && \ + tar -xzvf v4.3.4.tar.gz && \ + cd libzmq-4.3.4 && \ + mkdir build && \ + cd build && \ + cmake .. && \ + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# cppzmq setup +#=============================================================================== RUN \ - wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.zip && \ - unzip v4.3.4.zip && \ - cd libzmq-4.3.4 && \ + wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.9.0.tar.gz && \ + tar -xzvf v4.9.0.tar.gz && \ + cd cppzmq-4.9.0 && \ + mkdir build && \ + cd build && \ + cmake .. && \ + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# gsl setup +#=============================================================================== + +RUN \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libpcre3-dev + +RUN \ + wget https://github.com/imatix/gsl/archive/refs/tags/v4.1.4.tar.gz && \ + tar -xzvf v4.1.4.tar.gz && \ + cd gsl-4.1.4 && \ + make -j$(nproc) && \ + make install && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# libbitcoin-build setup +# libbitcoin-explorer setup +#=============================================================================== + +RUN \ + DEBIAN_FRONTEND=noninteractive apt-get install -y \ + libsodium-dev + +RUN \ + git clone --branch version3.8.0 --depth 1 https://gitlab.com/PBSA/peerplays-1.0/libbitcoin-explorer.git && \ + cd libbitcoin-explorer && \ + ./install.sh && \ + ldconfig && \ + rm -rf /home/peerplays/src/* + +#=============================================================================== +# Doxygen setup +#=============================================================================== + +RUN \ + sudo apt install -y bison flex && \ + wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \ + tar -xvf Release_1_8_17.tar.gz && \ + cd doxygen-Release_1_8_17 && \ mkdir build && \ cd build && \ cmake .. && \ @@ -93,18 +156,14 @@ RUN \ ldconfig #=============================================================================== -# cppzmq setup +# Perl setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ - wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.8.1.zip && \ - unzip v4.8.1.zip && \ - cd cppzmq-4.8.1 && \ - mkdir build && \ - cd build && \ - cmake .. && \ + wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \ + tar -xvf v5.30.0.tar.gz && \ + cd perl5-5.30.0 && \ + ./Configure -des && \ make -j$(nproc) install && \ ldconfig @@ -112,8 +171,6 @@ RUN \ # Peerplays setup #=============================================================================== -WORKDIR /home/peerplays/ - ## Clone Peerplays #RUN \ # git clone https://gitlab.com/PBSA/peerplays.git && \ @@ -129,6 +186,9 @@ ADD . peerplays # Configure Peerplays RUN \ cd peerplays && \ + git submodule update --init --recursive && \ + git symbolic-ref --short HEAD && \ + git log --oneline -n 5 && \ mkdir build && \ cd build && \ cmake -DCMAKE_BUILD_TYPE=Release .. @@ -142,8 +202,8 @@ WORKDIR /home/peerplays/peerplays-network # Setup Peerplays runimage RUN \ - ln -s /home/peerplays/peerplays/build/programs/cli_wallet/cli_wallet ./ && \ - ln -s /home/peerplays/peerplays/build/programs/witness_node/witness_node ./ + ln -s /home/peerplays/src/peerplays/build/programs/cli_wallet/cli_wallet ./ && \ + ln -s /home/peerplays/src/peerplays/build/programs/witness_node/witness_node ./ RUN ./witness_node --create-genesis-json genesis.json && \ rm genesis.json diff --git a/README.md b/README.md index a8d98021..cff50dc3 100644 --- a/README.md +++ b/README.md @@ -8,100 +8,41 @@ This is a quick introduction to get new developers and witnesses up to speed on Officially supported OS are Ubuntu 20.04 and Ubuntu 18.04. -## Ubuntu 20.04 +## Ubuntu 20.04 and 18.04 -Following dependencies are needed for a clean install of Ubuntu 20.04: +Following dependencies are needed for a clean install of Ubuntu 20.04 and Ubuntu 18.04: ``` sudo apt-get install \ - apt-utils autoconf bash build-essential ca-certificates clang-format cmake \ - dnsutils doxygen expect git graphviz libboost-all-dev libbz2-dev \ - libcurl4-openssl-dev libncurses-dev libsnappy-dev \ - libssl-dev libtool libzip-dev locales lsb-release mc nano net-tools ntp \ - openssh-server pkg-config perl python3 python3-jinja2 sudo \ + autoconf bash bison build-essential ca-certificates dnsutils expect flex git \ + graphviz libbz2-dev libcurl4-openssl-dev libncurses-dev libpcre3-dev \ + libsnappy-dev libsodium-dev libssl-dev libtool libzip-dev locales lsb-release \ + mc nano net-tools ntp openssh-server pkg-config python3 python3-jinja2 sudo \ systemd-coredump wget ``` -Install libzmq from source: +Boost libraries setup: ``` -wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.zip -unzip v4.3.4.zip -cd libzmq-4.3.4 -mkdir build -cd build -cmake .. -make -j$(nproc) -sudo make install -sudo ldconfig -``` - -Install cppzmq from source: -``` -wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.8.1.zip -unzip v4.8.1.zip -cd cppzmq-4.8.1 -mkdir build -cd build -cmake .. -make -j$(nproc) -sudo make install -sudo ldconfig -``` - -Building Peerplays -``` -git clone https://gitlab.com/PBSA/peerplays.git -cd peerplays -git submodule update --init --recursive - -# If you want to build Mainnet node -cmake -DCMAKE_BUILD_TYPE=Release - -# If you want to build Testnet node -cmake -DCMAKE_BUILD_TYPE=Release -DBUILD_PEERPLAYS_TESTNET=1 - -# Update -j flag depending on your current system specs; -# Recommended 4GB of RAM per 1 CPU core -# make -j2 for 8GB RAM -# make -j4 for 16GB RAM -# make -j8 for 32GB RAM -make -j$(nproc) - -sudo make install # this can install the executable files under /usr/local -``` - -## Ubuntu 18.04 - -Following dependencies are needed for a clean install of Ubuntu 18.04: -``` -sudo apt-get install \ - apt-utils autoconf bash build-essential ca-certificates clang-format \ - dnsutils doxygen expect git graphviz libbz2-dev \ - libcurl4-openssl-dev libncurses-dev libsnappy-dev \ - libssl-dev libtool libzip-dev locales lsb-release mc nano net-tools ntp \ - openssh-server pkg-config perl python3 python3-jinja2 sudo \ - systemd-coredump wget -``` - -Install Boost libraries from source -``` -wget -c 'http://sourceforge.net/projects/boost/files/boost/1.67.0/boost_1_67_0.tar.bz2/download' -O boost_1_67_0.tar.bz2 -tar xjf boost_1_67_0.tar.bz2 -cd boost_1_67_0/ +wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz +tar -xzf boost_1_72_0.tar.gz boost_1_72_0 +cd boost_1_72_0 ./bootstrap.sh +./b2 sudo ./b2 install +sudo ldconfig ``` -Install cmake +cmake setup: ``` -wget -c 'https://cmake.org/files/v3.23/cmake-3.23.1-linux-x86_64.sh' -O cmake-3.23.1-linux-x86_64.sh -chmod 755 ./cmake-3.23.1-linux-x86_64.sh -sudo ./cmake-3.23.1-linux-x86_64.sh --prefix=/usr/ --skip-license +wget https://github.com/Kitware/CMake/releases/download/v3.24.2/cmake-3.24.2-linux-x86_64.sh +chmod 755 ./cmake-3.24.2-linux-x86_64.sh +sudo ./cmake-3.24.2-linux-x86_64.sh --prefix=/usr --skip-license +cmake --version ``` -Install libzmq from source: +libzmq setup: ``` -wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.zip -unzip v4.3.4.zip +wget https://github.com/zeromq/libzmq/archive/refs/tags/v4.3.4.tar.gz +tar -xzvf v4.3.4.tar.gz cd libzmq-4.3.4 mkdir build cd build @@ -111,11 +52,11 @@ sudo make install sudo ldconfig ``` -Install cppzmq from source: +cppzmq setup: ``` -wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.8.1.zip -unzip v4.8.1.zip -cd cppzmq-4.8.1 +wget https://github.com/zeromq/cppzmq/archive/refs/tags/v4.9.0.tar.gz +tar -xzvf v4.9.0.tar.gz +cd cppzmq-4.9.0 mkdir build cd build cmake .. @@ -124,6 +65,48 @@ sudo make install sudo ldconfig ``` +gsl setup: +``` +wget https://github.com/imatix/gsl/archive/refs/tags/v4.1.4.tar.gz +tar -xzvf v4.1.4.tar.gz +cd gsl-4.1.4 +make -j$(nproc) +sudo make install +sudo ldconfig +``` + +libbitcoin-explorer setup: +``` +git clone --branch version3.8.0 --depth 1 https://gitlab.com/PBSA/peerplays-1.0/libbitcoin-explorer.git +cd libbitcoin-explorer +sudo ./install.sh +sudo ldconfig +``` + +Doxygen setup: +``` +wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz +tar -xvf Release_1_8_17.tar.gz +cd doxygen-Release_1_8_17 +mkdir build +cd build +cmake .. +make -j$(nproc) +sudo make install +sudo ldconfig +``` + +Perl setup: +``` +wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz +tar -xvf v5.30.0.tar.gz +cd perl5-5.30.0 +./Configure -des +make -j$(nproc) +sudo make install +sudo ldconfig +``` + Building Peerplays ``` git clone https://gitlab.com/PBSA/peerplays.git @@ -146,7 +129,6 @@ make -j$(nproc) sudo make install # this can install the executable files under /usr/local ``` - ## Docker images Install docker, and add current user to docker group. diff --git a/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index cf2355f1..f28a6cee 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -5,6 +5,7 @@ add_subdirectory( egenesis ) add_subdirectory( fc ) add_subdirectory( net ) add_subdirectory( plugins ) +add_subdirectory( sha3 ) add_subdirectory( time ) add_subdirectory( utilities ) add_subdirectory( wallet ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index f6b084f1..8dd18915 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -45,7 +45,6 @@ template class fc::api; template class fc::api; template class fc::api; template class fc::api; -template class fc::api; template class fc::api; template class fc::api; template class fc::api; @@ -90,8 +89,6 @@ void login_api::enable_api(const std::string &api_name) { _history_api = std::make_shared(_app); } else if (api_name == "network_node_api") { _network_node_api = std::make_shared(std::ref(_app)); - } else if (api_name == "crypto_api") { - _crypto_api = std::make_shared(); } else if (api_name == "asset_api") { _asset_api = std::make_shared(_app); } else if (api_name == "debug_api") { @@ -289,11 +286,6 @@ fc::api login_api::history() const { return *_history_api; } -fc::api login_api::crypto() const { - FC_ASSERT(_crypto_api); - return *_crypto_api; -} - fc::api login_api::asset() const { FC_ASSERT(_asset_api); return *_asset_api; @@ -522,55 +514,6 @@ vector history_api::get_market_history(std::string asset_a, std:: FC_CAPTURE_AND_RETHROW((asset_a)(asset_b)(bucket_seconds)(start)(end)) } -crypto_api::crypto_api(){}; - -commitment_type crypto_api::blind(const blind_factor_type &blind, uint64_t value) { - return fc::ecc::blind(blind, value); -} - -blind_factor_type crypto_api::blind_sum(const std::vector &blinds_in, uint32_t non_neg) { - return fc::ecc::blind_sum(blinds_in, non_neg); -} - -bool crypto_api::verify_sum(const std::vector &commits_in, const std::vector &neg_commits_in, int64_t excess) { - return fc::ecc::verify_sum(commits_in, neg_commits_in, excess); -} - -verify_range_result crypto_api::verify_range(const commitment_type &commit, const std::vector &proof) { - verify_range_result result; - result.success = fc::ecc::verify_range(result.min_val, result.max_val, commit, proof); - return result; -} - -std::vector crypto_api::range_proof_sign(uint64_t min_value, - const commitment_type &commit, - const blind_factor_type &commit_blind, - const blind_factor_type &nonce, - int8_t base10_exp, - uint8_t min_bits, - uint64_t actual_value) { - return fc::ecc::range_proof_sign(min_value, commit, commit_blind, nonce, base10_exp, min_bits, actual_value); -} - -verify_range_proof_rewind_result crypto_api::verify_range_proof_rewind(const blind_factor_type &nonce, - const commitment_type &commit, - const std::vector &proof) { - verify_range_proof_rewind_result result; - result.success = fc::ecc::verify_range_proof_rewind(result.blind_out, - result.value_out, - result.message_out, - nonce, - result.min_val, - result.max_val, - const_cast(commit), - proof); - return result; -} - -range_proof_info crypto_api::range_get_info(const std::vector &proof) { - return fc::ecc::range_get_info(proof); -} - // asset_api asset_api::asset_api(graphene::app::application &app) : _app(app), diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 34a49e3d..4f5f97c3 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -362,7 +362,6 @@ public: wild_access.allowed_apis.push_back("database_api"); wild_access.allowed_apis.push_back("network_broadcast_api"); wild_access.allowed_apis.push_back("history_api"); - wild_access.allowed_apis.push_back("crypto_api"); wild_access.allowed_apis.push_back("bookie_api"); wild_access.allowed_apis.push_back("affiliate_stats_api"); wild_access.allowed_apis.push_back("sidechain_api"); @@ -765,6 +764,10 @@ public: FC_CAPTURE_AND_RETHROW((block_id)) } + virtual fc::time_point_sec get_last_known_hardfork_time() override { + return _chain_db->_hardfork_times[_chain_db->_hardfork_times.size() - 1]; + } + /** * Returns the time a block was produced (if block_id = 0, returns genesis time). * If we don't know about the block, returns time_point_sec::min() @@ -914,7 +917,8 @@ void application::initialize(const fc::path &data_dir, const boost::program_opti wanted.insert("accounts_list"); wanted.insert("affiliate_stats"); } - wanted.insert("witness"); + if (!wanted.count("delayed_node") && !wanted.count("debug_witness") && !wanted.count("witness")) // explicitly requested delayed_node or debug_witness functionality suppresses witness functions + wanted.insert("witness"); wanted.insert("bookie"); int es_ah_conflict_counter = 0; @@ -946,7 +950,7 @@ void application::startup() { } std::shared_ptr application::get_plugin(const string &name) const { - return my->_active_plugins[name]; + return is_plugin_enabled(name) ? my->_active_plugins[name] : nullptr; } bool application::is_plugin_enabled(const string &name) const { diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index 1095bbeb..be60de78 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -71,6 +71,17 @@ std::string object_id_to_string(object_id_type id) { return object_id; } +signed_block_with_info::signed_block_with_info(){}; + +signed_block_with_info::signed_block_with_info(const signed_block &block) : + signed_block(block) { + block_id = id(); + signing_key = signee(); + transaction_ids.reserve(transactions.size()); + for (const processed_transaction &tx : transactions) + transaction_ids.push_back(tx.id()); +} + class database_api_impl : public std::enable_shared_from_this { public: database_api_impl(graphene::chain::database &db); @@ -89,6 +100,7 @@ public: optional get_block_header(uint32_t block_num) const; map> get_block_header_batch(const vector block_nums) const; optional get_block(uint32_t block_num) const; + optional get_block2(uint32_t block_num) const; vector> get_blocks(uint32_t block_num_from, uint32_t block_num_to) const; processed_transaction get_transaction(uint32_t block_num, uint32_t trx_in_block) const; @@ -184,6 +196,10 @@ public: fc::optional get_son_by_account(const std::string account_id_or_name) const; map lookup_son_accounts(const string &lower_bound_name, uint32_t limit) const; uint64_t get_son_count() const; + flat_map> get_active_sons(); + vector get_active_sons_by_sidechain(sidechain_type sidechain); + map> get_son_network_status(); + map get_son_network_status_by_sidechain(sidechain_type sidechain); // SON wallets optional get_active_son_wallet(); @@ -237,9 +253,6 @@ public: // Proposed transactions vector get_proposed_transactions(const std::string account_id_or_name) const; - // Blinded balances - vector get_blinded_balances(const flat_set &commitments) const; - // Tournaments vector get_tournaments_in_state(tournament_state state, uint32_t limit) const; vector get_tournaments(tournament_id_type stop, unsigned limit, tournament_id_type start); @@ -268,8 +281,9 @@ public: uint64_t nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const; nft_object nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const; nft_object nft_token_of_owner_by_index(const nft_metadata_id_type nft_metadata_id, const account_id_type owner, const uint64_t token_idx) const; - vector nft_get_all_tokens() const; - vector nft_get_tokens_by_owner(const account_id_type owner) const; + vector nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const; + vector nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const; + vector nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const; // Marketplace vector list_offers(const offer_id_type lower_id, uint32_t limit) const; @@ -290,6 +304,7 @@ public: uint32_t api_limit_get_limit_orders_by_account = 101; uint32_t api_limit_get_order_book = 50; uint32_t api_limit_all_offers_count = 100; + uint32_t api_limit_nft_tokens = 100; uint32_t api_limit_lookup_accounts = 1000; uint32_t api_limit_lookup_witness_accounts = 1000; uint32_t api_limit_lookup_committee_member_accounts = 1000; @@ -529,6 +544,17 @@ optional database_api_impl::get_block(uint32_t block_num) const { return _db.fetch_block_by_number(block_num); } +optional database_api::get_block2(uint32_t block_num) const { + return my->get_block2(block_num); +} + +optional database_api_impl::get_block2(uint32_t block_num) const { + auto result = _db.fetch_block_by_number(block_num); + if (result) + return signed_block_with_info(*result); + return {}; +} + vector> database_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to) const { return my->get_blocks(block_num_from, block_num_to); } @@ -1851,6 +1877,80 @@ uint64_t database_api_impl::get_son_count() const { return _db.get_index_type().indices().size(); } +flat_map> database_api::get_active_sons() { + return my->get_active_sons(); +} + +flat_map> database_api_impl::get_active_sons() { + return get_global_properties().active_sons; +} + +vector database_api::get_active_sons_by_sidechain(sidechain_type sidechain) { + return my->get_active_sons_by_sidechain(sidechain); +} + +vector database_api_impl::get_active_sons_by_sidechain(sidechain_type sidechain) { + const global_property_object &gpo = get_global_properties(); + + vector result; + + if (gpo.active_sons.find(sidechain) != gpo.active_sons.end()) { + result = gpo.active_sons.at(sidechain); + } + + return result; +} + +map> database_api::get_son_network_status() { + return my->get_son_network_status(); +} + +map> database_api_impl::get_son_network_status() { + map> result; + + for (auto active_sidechain_type : active_sidechain_types(_db.head_block_time())) { + result[active_sidechain_type] = get_son_network_status_by_sidechain(active_sidechain_type); + } + + return result; +} + +map database_api::get_son_network_status_by_sidechain(sidechain_type sidechain) { + return my->get_son_network_status_by_sidechain(sidechain); +} + +map database_api_impl::get_son_network_status_by_sidechain(sidechain_type sidechain) { + const global_property_object &gpo = get_global_properties(); + + map result; + + if (gpo.active_sons.find(sidechain) != gpo.active_sons.end()) { + for (const auto si : gpo.active_sons.at(sidechain)) { + const auto son_obj = si.son_id(_db); + const auto sso = son_obj.statistics(_db); + string status; + + if (sso.last_active_timestamp.find(sidechain) != sso.last_active_timestamp.end()) { + if (time_point_sec(sso.last_active_timestamp.at(sidechain) + fc::seconds(gpo.parameters.son_heartbeat_frequency())) > _db.head_block_time()) { + status = "OK, regular SON heartbeat"; + } else { + if (time_point_sec(sso.last_active_timestamp.at(sidechain) + fc::seconds(gpo.parameters.son_down_time())) > _db.head_block_time()) { + status = "OK, irregular SON heartbeat, but not triggering SON down proposal"; + } else { + status = "NOT OK, irregular SON heartbeat, triggering SON down proposal"; + } + } + } else { + status = "No heartbeats sent"; + } + + result[si.son_id] = status; + } + } + + return result; +} + ////////////////////////////////////////////////////////////////////// // // // SON Wallets // @@ -2086,7 +2186,9 @@ vector database_api_impl::lookup_vote_ids(const vector &v const auto &committee_idx = _db.get_index_type().indices().get(); const auto &for_worker_idx = _db.get_index_type().indices().get(); const auto &against_worker_idx = _db.get_index_type().indices().get(); - const auto &son_idx = _db.get_index_type().indices().get(); + const auto &son_bictoin_idx = _db.get_index_type().indices().get(); + const auto &son_hive_idx = _db.get_index_type().indices().get(); + const auto &son_ethereum_idx = _db.get_index_type().indices().get(); vector result; result.reserve(votes.size()); @@ -2122,15 +2224,30 @@ vector database_api_impl::lookup_vote_ids(const vector &v } break; } - case vote_id_type::son: { - auto itr = son_idx.find(id); - if (itr != son_idx.end()) + case vote_id_type::son_bitcoin: { + auto itr = son_bictoin_idx.find(id); + if (itr != son_bictoin_idx.end()) + result.emplace_back(variant(*itr, 5)); + else + result.emplace_back(variant()); + break; + } + case vote_id_type::son_hive: { + auto itr = son_hive_idx.find(id); + if (itr != son_hive_idx.end()) + result.emplace_back(variant(*itr, 5)); + else + result.emplace_back(variant()); + break; + } + case vote_id_type::son_ethereum: { + auto itr = son_ethereum_idx.find(id); + if (itr != son_ethereum_idx.end()) result.emplace_back(variant(*itr, 5)); else result.emplace_back(variant()); break; } - case vote_id_type::VOTE_TYPE_COUNT: break; // supress unused enum value warnings default: @@ -2155,12 +2272,24 @@ vector database_api_impl::get_votes_ids(const string &account_name votes_info database_api_impl::get_votes(const string &account_name_or_id) const { votes_info result; - const auto &votes_ids = get_votes_ids(account_name_or_id); - const auto &committee_ids = get_votes_objects(votes_ids); - const auto &witness_ids = get_votes_objects(votes_ids); - const auto &for_worker_ids = get_votes_objects(votes_ids); - const auto &against_worker_ids = get_votes_objects(votes_ids); - const auto &son_ids = get_votes_objects(votes_ids, 5); + const auto votes_ids = get_votes_ids(account_name_or_id); + const auto committee_ids = get_votes_objects(votes_ids); + const auto witness_ids = get_votes_objects(votes_ids); + const auto for_worker_ids = get_votes_objects(votes_ids); + const auto against_worker_ids = get_votes_objects(votes_ids); + const auto son_ids = [this, &votes_ids]() { + flat_map> son_ids; + const auto son_bitcoin_ids = get_votes_objects(votes_ids, 5); + if (!son_bitcoin_ids.empty()) + son_ids[sidechain_type::bitcoin] = std::move(son_bitcoin_ids); + const auto son_hive_ids = get_votes_objects(votes_ids, 5); + if (!son_hive_ids.empty()) + son_ids[sidechain_type::hive] = std::move(son_hive_ids); + const auto son_ethereum_ids = get_votes_objects(votes_ids, 5); + if (!son_ethereum_ids.empty()) + son_ids[sidechain_type::ethereum] = std::move(son_ethereum_ids); + return son_ids; + }(); //! Fill votes info if (!committee_ids.empty()) { @@ -2204,11 +2333,17 @@ votes_info database_api_impl::get_votes(const string &account_name_or_id) const } if (!son_ids.empty()) { - vector votes_for_sons; - votes_for_sons.reserve(son_ids.size()); - for (const auto &son : son_ids) { - const auto &son_obj = son.as(6); - votes_for_sons.emplace_back(votes_info_object{son_obj.vote_id, son_obj.id}); + flat_map> votes_for_sons; + for (const auto &son_sidechain_ids : son_ids) { + const auto &sidechain = son_sidechain_ids.first; + const auto &sidechain_ids = son_sidechain_ids.second; + votes_for_sons[sidechain].reserve(sidechain_ids.size()); + for (const auto &son : sidechain_ids) { + const auto &son_obj = son.as(6); + if (son_obj.get_sidechain_vote_id(sidechain).valid()) { + votes_for_sons[sidechain].emplace_back(votes_info_object{*son_obj.get_sidechain_vote_id(sidechain), son_obj.id}); + } + } } result.votes_for_sons = std::move(votes_for_sons); } @@ -2382,12 +2517,16 @@ voters_info database_api_impl::get_voters(const string &account_name_or_id) cons //! Info for son voters if (son_object) { - const auto &son_voters = get_voters_by_id(son_object->vote_id); - voters_info_object voters_for_son; - voters_for_son.vote_id = son_object->vote_id; - voters_for_son.voters.reserve(son_voters.size()); - for (const auto &voter : son_voters) { - voters_for_son.voters.emplace_back(voter.get_id()); + flat_map voters_for_son; + for (const auto &vote_id : son_object->sidechain_vote_ids) { + const auto &son_voters = get_voters_by_id(vote_id.second); + voters_info_object voters_for_sidechain_son; + voters_for_sidechain_son.vote_id = vote_id.second; + voters_for_sidechain_son.voters.reserve(son_voters.size()); + for (const auto &voter : son_voters) { + voters_for_sidechain_son.voters.emplace_back(voter.get_id()); + } + voters_for_son[vote_id.first] = std::move(voters_for_sidechain_son); } result.voters_for_son = std::move(voters_for_son); } @@ -2650,29 +2789,6 @@ vector database_api_impl::get_proposed_transactions(const std:: return result; } -////////////////////////////////////////////////////////////////////// -// // -// Blinded balances // -// // -////////////////////////////////////////////////////////////////////// - -vector database_api::get_blinded_balances(const flat_set &commitments) const { - return my->get_blinded_balances(commitments); -} - -vector database_api_impl::get_blinded_balances(const flat_set &commitments) const { - vector result; - result.reserve(commitments.size()); - const auto &bal_idx = _db.get_index_type(); - const auto &by_commitment_idx = bal_idx.indices().get(); - for (const auto &c : commitments) { - auto itr = by_commitment_idx.find(c); - if (itr != by_commitment_idx.end()) - result.push_back(*itr); - } - return result; -} - ////////////////////////////////////////////////////////////////////// // // // Tournament methods // @@ -2975,8 +3091,8 @@ uint64_t database_api::nft_get_total_supply(const nft_metadata_id_type nft_metad } uint64_t database_api_impl::nft_get_total_supply(const nft_metadata_id_type nft_metadata_id) const { - const auto &idx_nft_md = _db.get_index_type().indices().get(); - return idx_nft_md.size(); + const auto &idx_nft = _db.get_index_type().indices().get(); + return idx_nft.count(nft_metadata_id); } nft_object database_api::nft_token_by_index(const nft_metadata_id_type nft_metadata_id, const uint64_t token_idx) const { @@ -3013,30 +3129,61 @@ nft_object database_api_impl::nft_token_of_owner_by_index(const nft_metadata_id_ return {}; } -vector database_api::nft_get_all_tokens() const { - return my->nft_get_all_tokens(); +vector database_api::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const { + return my->nft_get_all_tokens(lower_id, limit); } -vector database_api_impl::nft_get_all_tokens() const { +vector database_api_impl::nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft tokens can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); + const auto &idx_nft = _db.get_index_type().indices().get(); vector result; - for (auto itr = idx_nft.begin(); itr != idx_nft.end(); ++itr) { - result.push_back(*itr); - } + result.reserve(limit); + auto itr = idx_nft.lower_bound(lower_id); + while (limit-- && itr != idx_nft.end()) + result.emplace_back(*itr++); return result; } -vector database_api::nft_get_tokens_by_owner(const account_id_type owner) const { - return my->nft_get_tokens_by_owner(owner); +vector database_api::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const { + return my->nft_get_tokens_by_owner(owner, lower_id, limit); } -vector database_api_impl::nft_get_tokens_by_owner(const account_id_type owner) const { +vector database_api_impl::nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft tokens can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); const auto &idx_nft = _db.get_index_type().indices().get(); auto idx_nft_range = idx_nft.equal_range(owner); vector result; - for (auto itr = idx_nft_range.first; itr != idx_nft_range.second; ++itr) { - result.push_back(*itr); - } + result.reserve(limit); + auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_object &obj) { + return !(obj.id.instance() < lower_id.instance); + }); + while (limit-- && itr != idx_nft_range.second) + result.emplace_back(*itr++); + return result; +} + +vector database_api::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const { + return my->nft_get_metadata_by_owner(owner, lower_id, limit); +} + +vector database_api_impl::nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const { + FC_ASSERT(limit <= api_limit_nft_tokens, + "Number of queried nft metadata objects can not be greater than ${configured_limit}", + ("configured_limit", api_limit_nft_tokens)); + const auto &idx_nft = _db.get_index_type().indices().get(); + auto idx_nft_range = idx_nft.equal_range(owner); + vector result; + result.reserve(limit); + auto itr = std::find_if(idx_nft_range.first, idx_nft_range.second, [&lower_id](const nft_metadata_object &obj) { + return !(obj.id.instance() < lower_id.instance); + }); + while (limit-- && itr != idx_nft_range.second) + result.emplace_back(*itr++); return result; } diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 98be16a6..68f2514a 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -289,33 +289,6 @@ private: std::function _on_pending_transaction; }; -class crypto_api { -public: - crypto_api(); - - fc::ecc::commitment_type blind(const fc::ecc::blind_factor_type &blind, uint64_t value); - - fc::ecc::blind_factor_type blind_sum(const std::vector &blinds_in, uint32_t non_neg); - - bool verify_sum(const std::vector &commits_in, const std::vector &neg_commits_in, int64_t excess); - - verify_range_result verify_range(const fc::ecc::commitment_type &commit, const std::vector &proof); - - std::vector range_proof_sign(uint64_t min_value, - const commitment_type &commit, - const blind_factor_type &commit_blind, - const blind_factor_type &nonce, - int8_t base10_exp, - uint8_t min_bits, - uint64_t actual_value); - - verify_range_proof_rewind_result verify_range_proof_rewind(const blind_factor_type &nonce, - const fc::ecc::commitment_type &commit, - const std::vector &proof); - - range_proof_info range_get_info(const std::vector &proof); -}; - /** * @brief */ @@ -359,7 +332,6 @@ extern template class fc::api; extern template class fc::api; extern template class fc::api; extern template class fc::api; -extern template class fc::api; extern template class fc::api; extern template class fc::api; @@ -394,8 +366,6 @@ public: fc::api history() const; /// @brief Retrieve the network node API fc::api network_node() const; - /// @brief Retrieve the cryptography API - fc::api crypto() const; /// @brief Retrieve the asset API fc::api asset() const; /// @brief Retrieve the debug API (if available) @@ -417,7 +387,6 @@ private: optional> _network_broadcast_api; optional> _network_node_api; optional> _history_api; - optional> _crypto_api; optional> _asset_api; optional> _debug_api; optional> _bookie_api; @@ -475,15 +444,6 @@ FC_API(graphene::app::network_node_api, (subscribe_to_pending_transactions) (unsubscribe_from_pending_transactions)) -FC_API(graphene::app::crypto_api, - (blind) - (blind_sum) - (verify_sum) - (verify_range) - (range_proof_sign) - (verify_range_proof_rewind) - (range_get_info)) - FC_API(graphene::app::asset_api, (get_asset_holders) (get_asset_holders_count) @@ -496,7 +456,6 @@ FC_API(graphene::app::login_api, (database) (history) (network_node) - (crypto) (asset) (debug) (bookie) diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index 679d8a4c..ddd909f1 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -82,6 +82,15 @@ using namespace std; class database_api_impl; +struct signed_block_with_info : public signed_block { + signed_block_with_info(); + signed_block_with_info(const signed_block &block); + signed_block_with_info(const signed_block_with_info &block) = default; + block_id_type block_id; + public_key_type signing_key; + vector transaction_ids; +}; + struct order { double price; double quote; @@ -202,6 +211,13 @@ public: */ optional get_block(uint32_t block_num) const; + /** + * @brief Retrieve a full, signed block, with some extra info + * @param block_num Height of the block to be returned + * @return the referenced block, or null if no matching block was found + */ + optional get_block2(uint32_t block_num) const; + /** * @brief Retrieve a list of signed blocks * @param block_num_from start @@ -675,6 +691,32 @@ public: */ uint64_t get_son_count() const; + /** + * @brief Get list of active sons + * @return List of active SONs + */ + flat_map> get_active_sons(); + + /** + * @brief Get list of active sons + * @param sidechain Sidechain type [bitcoin|ethereum|hive] + * @return List of active SONs + */ + vector get_active_sons_by_sidechain(sidechain_type sidechain); + + /** + * @brief Get SON network status + * @return SON network status description for a given sidechain type + */ + map> get_son_network_status(); + + /** + * @brief Get SON network status + * @param sidechain Sidechain type [bitcoin|ethereum|hive] + * @return SON network status description for a given sidechain type + */ + map get_son_network_status_by_sidechain(sidechain_type sidechain); + ///////////////////////// // SON Wallets // ///////////////////////// @@ -877,15 +919,6 @@ public: */ vector get_proposed_transactions(const std::string account_id_or_name) const; - ////////////////////// - // Blinded balances // - ////////////////////// - - /** - * @return the set of blinded balance objects by commitment ID - */ - vector get_blinded_balances(const flat_set &commitments) const; - ///////////////// // Tournaments // ///////////////// @@ -1010,14 +1043,25 @@ public: * @brief Returns list of all available NTF's * @return List of all available NFT's */ - vector nft_get_all_tokens() const; + vector nft_get_all_tokens(const nft_id_type lower_id, uint32_t limit) const; /** * @brief Returns NFT's owned by owner * @param owner NFT owner + * @param lower_id ID of the first NFT to return + * @param limit Maximum number of results to return * @return List of NFT owned by owner */ - vector nft_get_tokens_by_owner(const account_id_type owner) const; + vector nft_get_tokens_by_owner(const account_id_type owner, const nft_id_type lower_id, uint32_t limit) const; + + /** + * @brief Returns NFT metadata owned by owner + * @param owner NFT owner + * @param lower_id ID of the first NFT metadata to return + * @param limit Maximum number of results to return + * @return List of NFT owned by owner + */ + vector nft_get_metadata_by_owner(const account_id_type owner, const nft_metadata_id_type lower_id, uint32_t limit) const; ////////////////// // MARKET PLACE // @@ -1047,6 +1091,8 @@ extern template class fc::api; // clang-format off +FC_REFLECT_DERIVED(graphene::app::signed_block_with_info, (graphene::chain::signed_block), (block_id)(signing_key)(transaction_ids)); + FC_REFLECT(graphene::app::order, (price)(quote)(base)); FC_REFLECT(graphene::app::order_book, (base)(quote)(bids)(asks)); FC_REFLECT(graphene::app::market_ticker, (base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume)); @@ -1069,6 +1115,7 @@ FC_API(graphene::app::database_api, (get_block_header) (get_block_header_batch) (get_block) + (get_block2) (get_blocks) (get_transaction) (get_recent_transaction_by_id) @@ -1158,6 +1205,10 @@ FC_API(graphene::app::database_api, (get_son_by_account) (lookup_son_accounts) (get_son_count) + (get_active_sons) + (get_active_sons_by_sidechain) + (get_son_network_status) + (get_son_network_status_by_sidechain) // SON wallets (get_active_son_wallet) @@ -1198,9 +1249,6 @@ FC_API(graphene::app::database_api, // Proposed transactions (get_proposed_transactions) - // Blinded balances - (get_blinded_balances) - // Tournaments (get_tournaments_in_state) (get_tournaments_by_state) @@ -1231,6 +1279,7 @@ FC_API(graphene::app::database_api, (nft_token_of_owner_by_index) (nft_get_all_tokens) (nft_get_tokens_by_owner) + (nft_get_metadata_by_owner) // Marketplace (list_offers) diff --git a/libraries/chain/account_evaluator.cpp b/libraries/chain/account_evaluator.cpp index aa199c84..884bba2d 100644 --- a/libraries/chain/account_evaluator.cpp +++ b/libraries/chain/account_evaluator.cpp @@ -53,7 +53,54 @@ void verify_authority_accounts( const database& db, const authority& a ) } } -void verify_account_votes( const database& db, const account_options& options ) +// Overwrites the num_son values from the origin to the destination for those sidechains which are found in the origin. +// Keeps the values of num_son for the sidechains which are found in the destination, but not in the origin. +// Returns false if an error is detected. +bool merge_num_sons( flat_map& destination, + const flat_map& origin, + fc::optional head_block_time = {}) +{ + const auto active_sidechains = head_block_time.valid() ? active_sidechain_types(*head_block_time) : all_sidechain_types; + bool success = true; + + for (const auto &ns : origin) + { + destination[ns.first] = ns.second; + if (active_sidechains.find(ns.first) == active_sidechains.end()) + { + success = false; + } + } + + return success; +} + +flat_map count_SON_votes_per_sidechain( const flat_set& votes ) +{ + flat_map SON_votes_per_sidechain = account_options::ext::empty_num_son(); + + for (const auto &vote : votes) + { + switch (vote.type()) + { + case vote_id_type::son_bitcoin: + SON_votes_per_sidechain[sidechain_type::bitcoin]++; + break; + case vote_id_type::son_hive: + SON_votes_per_sidechain[sidechain_type::hive]++; + break; + case vote_id_type::son_ethereum: + SON_votes_per_sidechain[sidechain_type::ethereum]++; + break; + default: + break; + } + } + + return SON_votes_per_sidechain; +} + +void verify_account_votes( const database& db, const account_options& options, fc::optional account = {} ) { // ensure account's votes satisfy requirements // NB only the part of vote checking that requires chain state is here, @@ -62,10 +109,47 @@ void verify_account_votes( const database& db, const account_options& options ) const auto& gpo = db.get_global_properties(); const auto& chain_params = gpo.parameters; + FC_ASSERT( db.find_object(options.voting_account), "Invalid proxy account specified." ); + FC_ASSERT( options.num_witness <= chain_params.maximum_witness_count, "Voted for more witnesses than currently allowed (${c})", ("c", chain_params.maximum_witness_count) ); FC_ASSERT( options.num_committee <= chain_params.maximum_committee_count, "Voted for more committee members than currently allowed (${c})", ("c", chain_params.maximum_committee_count) ); + FC_ASSERT( chain_params.extensions.value.maximum_son_count.valid() , "Invalid maximum son count" ); + + flat_map merged_num_sons = account_options::ext::empty_num_son(); + + // Merge with existing account if exists + if ( account.valid() && account->options.extensions.value.num_son.valid()) + { + merge_num_sons( merged_num_sons, *account->options.extensions.value.num_son, db.head_block_time() ); + } + + // Apply update operation on top + if ( options.extensions.value.num_son.valid() ) + { + merge_num_sons( merged_num_sons, *options.extensions.value.num_son, db.head_block_time() ); + } + + for(const auto& num_sons : merged_num_sons) + { + FC_ASSERT( num_sons.second <= *chain_params.extensions.value.maximum_son_count, + "Voted for more sons than currently allowed (${c})", ("c", *chain_params.extensions.value.maximum_son_count) ); + } + + // Count the votes for SONs and confirm that the account did not vote for less SONs than num_son + flat_map SON_votes_per_sidechain = count_SON_votes_per_sidechain(options.votes); + + for (const auto& number_of_votes : SON_votes_per_sidechain) + { + // Number of votes of account_options are also checked in account_options::do_evaluate, + // but there we are checking the value before merging num_sons, so the values should be checked again + const auto sidechain = number_of_votes.first; + FC_ASSERT( number_of_votes.second >= merged_num_sons[sidechain], + "Voted for less sons than specified in num_son (votes ${v} < num_son ${ns}) for sidechain ${s}", + ("v", number_of_votes.second) ("ns", merged_num_sons[sidechain]) ("s", sidechain) ); + } + FC_ASSERT( db.find_object(options.voting_account), "Invalid proxy account specified." ); uint32_t max_vote_id = gpo.next_available_vote_id; @@ -179,6 +263,13 @@ object_id_type account_create_evaluator::do_apply( const account_create_operatio obj.owner = o.owner; obj.active = o.active; obj.options = o.options; + + obj.options.extensions.value.num_son = account_options::ext::empty_num_son(); + if ( o.options.extensions.value.num_son.valid() ) + { + merge_num_sons( *obj.options.extensions.value.num_son, *o.options.extensions.value.num_son ); + } + obj.statistics = d.create([&obj](account_statistics_object& s){ s.owner = obj.id; s.name = obj.name; @@ -278,7 +369,7 @@ void_result account_update_evaluator::do_evaluate( const account_update_operatio acnt = &o.account(d); if( o.new_options.valid() ) - verify_account_votes( d, *o.new_options ); + verify_account_votes( d, *o.new_options, *acnt ); return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } @@ -317,7 +408,31 @@ void_result account_update_evaluator::do_apply( const account_update_operation& a.active = *o.active; a.top_n_control_flags = 0; } - if( o.new_options ) a.options = *o.new_options; + + // New num_son structure initialized to 0 + flat_map new_num_son = account_options::ext::empty_num_son(); + + // If num_son of existing object is valid, we should merge the existing data + if ( a.options.extensions.value.num_son.valid() ) + { + merge_num_sons( new_num_son, *a.options.extensions.value.num_son ); + } + + // If num_son of the operation are valid, they should merge the existing data + if ( o.new_options ) + { + const auto new_options = *o.new_options; + + if ( new_options.extensions.value.num_son.valid() ) + { + merge_num_sons( new_num_son, *new_options.extensions.value.num_son ); + } + + a.options = *o.new_options; + } + + a.options.extensions.value.num_son = new_num_son; + if( o.extensions.value.owner_special_authority.valid() ) { a.owner_special_authority = *(o.extensions.value.owner_special_authority); diff --git a/libraries/chain/confidential_evaluator.cpp b/libraries/chain/confidential_evaluator.cpp index 9946b492..0526c116 100644 --- a/libraries/chain/confidential_evaluator.cpp +++ b/libraries/chain/confidential_evaluator.cpp @@ -33,149 +33,163 @@ namespace graphene { namespace chain { void_result transfer_to_blind_evaluator::do_evaluate( const transfer_to_blind_operation& o ) { try { - const auto& d = db(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) + { + const auto& atype = o.amount.asset_id(d); + FC_ASSERT( atype.allow_confidential() ); + FC_ASSERT( !atype.is_transfer_restricted() ); + FC_ASSERT( !(atype.options.flags & white_list) ); - const auto& atype = o.amount.asset_id(db()); - FC_ASSERT( atype.allow_confidential() ); - FC_ASSERT( !atype.is_transfer_restricted() ); - FC_ASSERT( !(atype.options.flags & white_list) ); + for( const auto& out : o.outputs ) + { + for( const auto& a : out.owner.account_auths ) + a.first(d); // verify all accounts exist and are valid + } + } - for( const auto& out : o.outputs ) - { - for( const auto& a : out.owner.account_auths ) - a.first(d); // verify all accounts exist and are valid - } - return void_result(); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result transfer_to_blind_evaluator::do_apply( const transfer_to_blind_operation& o ) +void_result transfer_to_blind_evaluator::do_apply( const transfer_to_blind_operation& o ) { try { - db().adjust_balance( o.from, -o.amount ); + if( db().head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + db().adjust_balance(o.from, -o.amount); - const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset - db().modify( add, [&]( asset_dynamic_data_object& obj ){ - obj.confidential_supply += o.amount.amount; - FC_ASSERT( obj.confidential_supply >= 0 ); - }); - for( const auto& out : o.outputs ) - { - db().create( [&]( blinded_balance_object& obj ){ - obj.asset_id = o.amount.asset_id; - obj.owner = out.owner; - obj.commitment = out.commitment; - }); - } - return void_result(); + const auto &add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset + db().modify(add, [&](asset_dynamic_data_object &obj) { + obj.confidential_supply += o.amount.amount; + FC_ASSERT(obj.confidential_supply >= 0); + }); + for (const auto &out : o.outputs) { + db().create([&](blinded_balance_object &obj) { + obj.asset_id = o.amount.asset_id; + obj.owner = out.owner; + obj.commitment = out.commitment; + }); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } void transfer_to_blind_evaluator::pay_fee() { - if( db().head_block_time() >= HARDFORK_563_TIME ) - pay_fba_fee( fba_accumulator_id_transfer_to_blind ); - else - generic_evaluator::pay_fee(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + if (d.head_block_time() >= HARDFORK_563_TIME) + pay_fba_fee(fba_accumulator_id_transfer_to_blind); + else + generic_evaluator::pay_fee(); + } } void_result transfer_from_blind_evaluator::do_evaluate( const transfer_from_blind_operation& o ) { try { - const auto& d = db(); - o.fee.asset_id(d); // verify fee is a legit asset - const auto& bbi = d.get_index_type(); - const auto& cidx = bbi.indices().get(); - for( const auto& in : o.inputs ) - { - auto itr = cidx.find( in.commitment ); - FC_ASSERT( itr != cidx.end() ); - FC_ASSERT( itr->asset_id == o.fee.asset_id ); - FC_ASSERT( itr->owner == in.owner ); - } - return void_result(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + o.fee.asset_id(d); // verify fee is a legit asset + const auto &bbi = d.get_index_type(); + const auto &cidx = bbi.indices().get(); + for (const auto &in : o.inputs) { + auto itr = cidx.find(in.commitment); + FC_ASSERT(itr != cidx.end()); + FC_ASSERT(itr->asset_id == o.fee.asset_id); + FC_ASSERT(itr->owner == in.owner); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result transfer_from_blind_evaluator::do_apply( const transfer_from_blind_operation& o ) +void_result transfer_from_blind_evaluator::do_apply( const transfer_from_blind_operation& o ) { try { - db().adjust_balance( o.fee_payer(), o.fee ); - db().adjust_balance( o.to, o.amount ); - const auto& bbi = db().get_index_type(); - const auto& cidx = bbi.indices().get(); - for( const auto& in : o.inputs ) - { - auto itr = cidx.find( in.commitment ); - FC_ASSERT( itr != cidx.end() ); - db().remove( *itr ); - } - const auto& add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset - db().modify( add, [&]( asset_dynamic_data_object& obj ){ - obj.confidential_supply -= o.amount.amount + o.fee.amount; - FC_ASSERT( obj.confidential_supply >= 0 ); - }); - return void_result(); + if( db().head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + db().adjust_balance(o.fee_payer(), o.fee); + db().adjust_balance(o.to, o.amount); + const auto &bbi = db().get_index_type(); + const auto &cidx = bbi.indices().get(); + for (const auto &in : o.inputs) { + auto itr = cidx.find(in.commitment); + FC_ASSERT(itr != cidx.end()); + db().remove(*itr); + } + const auto &add = o.amount.asset_id(db()).dynamic_asset_data_id(db()); // verify fee is a legit asset + db().modify(add, [&](asset_dynamic_data_object &obj) { + obj.confidential_supply -= o.amount.amount + o.fee.amount; + FC_ASSERT(obj.confidential_supply >= 0); + }); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } void transfer_from_blind_evaluator::pay_fee() { - if( db().head_block_time() >= HARDFORK_563_TIME ) - pay_fba_fee( fba_accumulator_id_transfer_from_blind ); - else - generic_evaluator::pay_fee(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + if (d.head_block_time() >= HARDFORK_563_TIME) + pay_fba_fee(fba_accumulator_id_transfer_from_blind); + else + generic_evaluator::pay_fee(); + } } void_result blind_transfer_evaluator::do_evaluate( const blind_transfer_operation& o ) { try { - const auto& d = db(); - o.fee.asset_id(db()); // verify fee is a legit asset - const auto& bbi = db().get_index_type(); - const auto& cidx = bbi.indices().get(); - for( const auto& out : o.outputs ) - { - for( const auto& a : out.owner.account_auths ) - a.first(d); // verify all accounts exist and are valid - } - for( const auto& in : o.inputs ) - { - auto itr = cidx.find( in.commitment ); - GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment",in.commitment) ); - FC_ASSERT( itr->asset_id == o.fee.asset_id ); - FC_ASSERT( itr->owner == in.owner ); - } - return void_result(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + o.fee.asset_id(d); // verify fee is a legit asset + const auto &bbi = d.get_index_type(); + const auto &cidx = bbi.indices().get(); + for (const auto &out : o.outputs) { + for (const auto &a : out.owner.account_auths) + a.first(d); // verify all accounts exist and are valid + } + for (const auto &in : o.inputs) { + auto itr = cidx.find(in.commitment); + GRAPHENE_ASSERT(itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment", in.commitment)); + FC_ASSERT(itr->asset_id == o.fee.asset_id); + FC_ASSERT(itr->owner == in.owner); + } + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } -void_result blind_transfer_evaluator::do_apply( const blind_transfer_operation& o ) +void_result blind_transfer_evaluator::do_apply( const blind_transfer_operation& o ) { try { - db().adjust_balance( o.fee_payer(), o.fee ); // deposit the fee to the temp account - const auto& bbi = db().get_index_type(); - const auto& cidx = bbi.indices().get(); - for( const auto& in : o.inputs ) - { - auto itr = cidx.find( in.commitment ); - GRAPHENE_ASSERT( itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment",in.commitment) ); - db().remove( *itr ); - } - for( const auto& out : o.outputs ) - { - db().create( [&]( blinded_balance_object& obj ){ - obj.asset_id = o.fee.asset_id; - obj.owner = out.owner; - obj.commitment = out.commitment; - }); - } - const auto& add = o.fee.asset_id(db()).dynamic_asset_data_id(db()); - db().modify( add, [&]( asset_dynamic_data_object& obj ){ - obj.confidential_supply -= o.fee.amount; - FC_ASSERT( obj.confidential_supply >= 0 ); - }); - - return void_result(); + if( db().head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + db().adjust_balance(o.fee_payer(), o.fee); // deposit the fee to the temp account + const auto &bbi = db().get_index_type(); + const auto &cidx = bbi.indices().get(); + for (const auto &in : o.inputs) { + auto itr = cidx.find(in.commitment); + GRAPHENE_ASSERT(itr != cidx.end(), blind_transfer_unknown_commitment, "", ("commitment", in.commitment)); + db().remove(*itr); + } + for (const auto &out : o.outputs) { + db().create([&](blinded_balance_object &obj) { + obj.asset_id = o.fee.asset_id; + obj.owner = out.owner; + obj.commitment = out.commitment; + }); + } + const auto &add = o.fee.asset_id(db()).dynamic_asset_data_id(db()); + db().modify(add, [&](asset_dynamic_data_object &obj) { + obj.confidential_supply -= o.fee.amount; + FC_ASSERT(obj.confidential_supply >= 0); + }); + } + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } void blind_transfer_evaluator::pay_fee() { - if( db().head_block_time() >= HARDFORK_563_TIME ) - pay_fba_fee( fba_accumulator_id_blind_transfer ); - else - generic_evaluator::pay_fee(); + const auto& d = db(); + if( d.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + if (d.head_block_time() >= HARDFORK_563_TIME) + pay_fba_fee(fba_accumulator_id_blind_transfer); + else + generic_evaluator::pay_fee(); + } } } } // graphene::chain diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index c475813b..79b24464 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -40,8 +40,10 @@ #include #include #include +#include #include +#include namespace { @@ -160,10 +162,13 @@ void database::check_transaction_for_duplicated_operations(const signed_transact existed_operations_digests.insert( proposed_operations_digests.begin(), proposed_operations_digests.end() ); }); - for (auto& pending_transaction: _pending_tx) { - auto proposed_operations_digests = gather_proposed_operations_digests(pending_transaction); - existed_operations_digests.insert(proposed_operations_digests.begin(), proposed_operations_digests.end()); + const std::lock_guard pending_tx_lock{_pending_tx_mutex}; + for (auto &pending_transaction : _pending_tx) + { + auto proposed_operations_digests = gather_proposed_operations_digests(pending_transaction); + existed_operations_digests.insert(proposed_operations_digests.begin(), proposed_operations_digests.end()); + } } auto proposed_operations_digests = gather_proposed_operations_digests(trx); @@ -185,7 +190,12 @@ bool database::push_block(const signed_block& new_block, uint32_t skip) bool result; detail::with_skip_flags( *this, skip, [&]() { - detail::without_pending_transactions( *this, std::move(_pending_tx), + std::vector pending_tx = [this] { + const std::lock_guard pending_tx_lock{_pending_tx_mutex}; + return std::move(_pending_tx); + }(); + + detail::without_pending_transactions( *this, std::move(pending_tx), [&]() { result = _push_block(new_block); @@ -196,6 +206,9 @@ bool database::push_block(const signed_block& new_block, uint32_t skip) bool database::_push_block(const signed_block& new_block) { try { + boost::filesystem::space_info si = boost::filesystem::space(get_data_dir()); + FC_ASSERT((si.available) > 104857600, "Rejecting block due to low disk space"); // 104857600 bytes = 100 MB + uint32_t skip = get_node_properties().skip_flags; const auto now = fc::time_point::now().sec_since_epoch(); @@ -382,17 +395,26 @@ processed_transaction database::_push_transaction( const signed_transaction& trx { // If this is the first transaction pushed after applying a block, start a new undo session. // This allows us to quickly rewind to the clean state of the head block, in case a new block arrives. - if( !_pending_tx_session.valid() ) - _pending_tx_session = _undo_db.start_undo_session(); + { + const std::lock_guard pending_tx_session_lock{_pending_tx_session_mutex}; + if (!_pending_tx_session.valid()) { + const std::lock_guard undo_db_lock{_undo_db_mutex}; + _pending_tx_session = _undo_db.start_undo_session(); + } + } // Create a temporary undo session as a child of _pending_tx_session. // The temporary session will be discarded by the destructor if // _apply_transaction fails. If we make it to merge(), we // apply the changes. + const std::lock_guard undo_db_lock{_undo_db_mutex}; auto temp_session = _undo_db.start_undo_session(); - auto processed_trx = _apply_transaction( trx ); - _pending_tx.push_back(processed_trx); + auto processed_trx = _apply_transaction(trx); + { + const std::lock_guard pending_tx_lock{_pending_tx_mutex}; + _pending_tx.push_back(processed_trx); + } // notify_changed_objects(); // The transaction applied successfully. Merge its changes into the pending block session. @@ -405,6 +427,7 @@ processed_transaction database::_push_transaction( const signed_transaction& trx processed_transaction database::validate_transaction( const signed_transaction& trx ) { + const std::lock_guard undo_db_lock{_undo_db_mutex}; auto session = _undo_db.start_undo_session(); return _apply_transaction( trx ); } @@ -504,47 +527,52 @@ signed_block database::_generate_block( // the value of the "when" variable is known, which means we need to // re-apply pending transactions in this method. // - _pending_tx_session.reset(); - _pending_tx_session = _undo_db.start_undo_session(); + { + const std::lock_guard pending_tx_session_lock{_pending_tx_session_mutex}; + _pending_tx_session.reset(); + _pending_tx_session = _undo_db.start_undo_session(); + } uint64_t postponed_tx_count = 0; // pop pending state (reset to head block state) - for( const processed_transaction& tx : _pending_tx ) { - size_t new_total_size = total_block_size + fc::raw::pack_size( tx ); + const std::lock_guard pending_tx_lock{_pending_tx_mutex}; + for (const processed_transaction &tx : _pending_tx) { + size_t new_total_size = total_block_size + fc::raw::pack_size(tx); - // postpone transaction if it would make block too big - if( new_total_size >= maximum_block_size ) - { - postponed_tx_count++; - continue; - } + // postpone transaction if it would make block too big + if (new_total_size >= maximum_block_size) { + postponed_tx_count++; + continue; + } - try - { - auto temp_session = _undo_db.start_undo_session(); - processed_transaction ptx = _apply_transaction( tx ); - temp_session.merge(); + try { + auto temp_session = _undo_db.start_undo_session(); + processed_transaction ptx = _apply_transaction(tx); + temp_session.merge(); - // We have to recompute pack_size(ptx) because it may be different - // than pack_size(tx) (i.e. if one or more results increased - // their size) - total_block_size += fc::raw::pack_size( ptx ); - pending_block.transactions.push_back( ptx ); - } - catch ( const fc::exception& e ) - { - // Do nothing, transaction will not be re-applied - wlog( "Transaction was not processed while generating block due to ${e}", ("e", e) ); - wlog( "The transaction was ${t}", ("t", tx) ); + // We have to recompute pack_size(ptx) because it may be different + // than pack_size(tx) (i.e. if one or more results increased + // their size) + total_block_size += fc::raw::pack_size(ptx); + pending_block.transactions.push_back(ptx); + } catch (const fc::exception &e) { + // Do nothing, transaction will not be re-applied + wlog("Transaction was not processed while generating block due to ${e}", ("e", e)); + wlog("The transaction was ${t}", ("t", tx)); + } } } + if( postponed_tx_count > 0 ) { wlog( "Postponed ${n} transactions due to block size limit", ("n", postponed_tx_count) ); } - _pending_tx_session.reset(); + { + const std::lock_guard pending_tx_session_lock{_pending_tx_session_mutex}; + _pending_tx_session.reset(); + } // We have temporarily broken the invariant that // _pending_tx_session is the result of applying _pending_tx, as @@ -592,7 +620,11 @@ signed_block database::_generate_block( */ void database::pop_block() { try { - _pending_tx_session.reset(); + { + const std::lock_guard pending_tx_session_lock{_pending_tx_session_mutex}; + _pending_tx_session.reset(); + } + auto head_id = head_block_id(); optional head_block = fetch_block_by_id( head_id ); GRAPHENE_ASSERT( head_block.valid(), pop_empty_chain, "there are no blocks to pop" ); @@ -606,6 +638,8 @@ void database::pop_block() void database::clear_pending() { try { + const std::lock_guard pending_tx_lock{_pending_tx_mutex}; + const std::lock_guard pending_tx_session_lock{_pending_tx_session_mutex}; assert( (_pending_tx.size() == 0) || _pending_tx_session.valid() ); _pending_tx.clear(); _pending_tx_session.reset(); @@ -705,8 +739,11 @@ void database::_apply_block( const signed_block& next_block ) if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) { update_witness_schedule(next_block); - if(global_props.active_sons.size() > 0) { - update_son_schedule(next_block); + + for(const auto& active_sons : global_props.active_sons) { + if(!active_sons.second.empty()) { + update_son_schedule(active_sons.first, next_block); + } } } @@ -721,7 +758,7 @@ void database::_apply_block( const signed_block& next_block ) check_ending_lotteries(); check_ending_nft_lotteries(); - + create_block_summary(next_block); place_delayed_bets(); // must happen after update_global_dynamic_data() updates the time clear_expired_transactions(); @@ -739,11 +776,15 @@ void database::_apply_block( const signed_block& next_block ) // TODO: figure out if we could collapse this function into // update_global_dynamic_data() as perhaps these methods only need // to be called for header validation? + update_maintenance_flag( maint_needed ); if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) { update_witness_schedule(); - if(global_props.active_sons.size() > 0) { - update_son_schedule(); + + for(const auto& active_sidechain_type : active_sidechain_types(dynamic_global_props.time)) { + if(global_props.active_sons.at(active_sidechain_type).size() > 0) { + update_son_schedule(active_sidechain_type); + } } } diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 78740c9b..af3635c0 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -222,17 +222,32 @@ std::set database::get_sons_to_be_deregistered() for( auto& son : son_idx ) { - if(son.status == son_status::in_maintenance) + bool need_to_be_deregistered = true; + for(const auto& status : son.statuses) { - auto stats = son.statistics(*this); - // TODO : We need to add a function that returns if we can deregister SON - // i.e. with introduction of PW code, we have to make a decision if the SON - // is needed for release of funds from the PW - if(head_block_time() - stats.last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time())) + const auto& sidechain = status.first; + if(status.second != son_status::in_maintenance) + need_to_be_deregistered = false; + + if(need_to_be_deregistered) { - ret.insert(son.id); + auto stats = son.statistics(*this); + + // TODO : We need to add a function that returns if we can deregister SON + // i.e. with introduction of PW code, we have to make a decision if the SON + // is needed for release of funds from the PW + if(stats.last_active_timestamp.contains(sidechain)) { + if (head_block_time() - stats.last_active_timestamp.at(sidechain) < fc::seconds(get_global_properties().parameters.son_deregister_time())) { + need_to_be_deregistered = false; + } + } } } + + if(need_to_be_deregistered) + { + ret.insert(son.id); + } } return ret; } @@ -289,28 +304,50 @@ bool database::is_son_dereg_valid( son_id_type son_id ) return false; } - return (son->status == son_status::in_maintenance && - (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time()))); + bool status_son_dereg_valid = true; + for (const auto &active_sidechain_type : active_sidechain_types(head_block_time())) { + if(son->statuses.at(active_sidechain_type) != son_status::in_maintenance) + status_son_dereg_valid = false; + + if(status_son_dereg_valid) + { + if(son->statistics(*this).last_active_timestamp.contains(active_sidechain_type)) { + if (head_block_time() - son->statistics(*this).last_active_timestamp.at(active_sidechain_type) < fc::seconds(get_global_properties().parameters.son_deregister_time())) { + status_son_dereg_valid = false; + } + } + } + } + + return status_son_dereg_valid; } -bool database::is_son_active( son_id_type son_id ) +bool database::is_son_active( sidechain_type type, son_id_type son_id ) { const auto& son_idx = get_index_type().indices().get< by_id >(); auto son = son_idx.find( son_id ); - if(son == son_idx.end()) - { + if(son == son_idx.end()) { return false; } const global_property_object& gpo = get_global_properties(); + if(!gpo.active_sons.contains(type)) { + return false; + } + + const auto& gpo_as = gpo.active_sons.at(type); vector active_son_ids; - active_son_ids.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + active_son_ids.reserve(gpo_as.size()); + std::transform(gpo_as.cbegin(), gpo_as.cend(), std::inserter(active_son_ids, active_son_ids.end()), - [](const son_info& swi) { + [](const son_sidechain_info& swi) { return swi.son_id; }); + if(active_son_ids.empty()) { + return false; + } + auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), son_id); return (it_son != active_son_ids.end()); } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 724cad85..05326d16 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -329,10 +329,54 @@ void database::initialize_evaluators() register_evaluator(); } + +void database::initialize_hardforks() +{ + _hardfork_times.emplace_back(HARDFORK_357_TIME); + _hardfork_times.emplace_back(HARDFORK_359_TIME); + _hardfork_times.emplace_back(HARDFORK_385_TIME); + _hardfork_times.emplace_back(HARDFORK_409_TIME); + _hardfork_times.emplace_back(HARDFORK_413_TIME); + _hardfork_times.emplace_back(HARDFORK_415_TIME); + _hardfork_times.emplace_back(HARDFORK_416_TIME); + _hardfork_times.emplace_back(HARDFORK_419_TIME); + _hardfork_times.emplace_back(HARDFORK_436_TIME); + _hardfork_times.emplace_back(HARDFORK_445_TIME); + _hardfork_times.emplace_back(HARDFORK_453_TIME); + _hardfork_times.emplace_back(HARDFORK_480_TIME); + _hardfork_times.emplace_back(HARDFORK_483_TIME); + _hardfork_times.emplace_back(HARDFORK_516_TIME); + _hardfork_times.emplace_back(HARDFORK_533_TIME); + _hardfork_times.emplace_back(HARDFORK_538_TIME); + _hardfork_times.emplace_back(HARDFORK_555_TIME); + _hardfork_times.emplace_back(HARDFORK_563_TIME); + _hardfork_times.emplace_back(HARDFORK_572_TIME); + _hardfork_times.emplace_back(HARDFORK_599_TIME); + _hardfork_times.emplace_back(HARDFORK_607_TIME); + _hardfork_times.emplace_back(HARDFORK_613_TIME); + _hardfork_times.emplace_back(HARDFORK_615_TIME); + _hardfork_times.emplace_back(HARDFORK_999_TIME); + _hardfork_times.emplace_back(HARDFORK_1000_TIME); + _hardfork_times.emplace_back(HARDFORK_1001_TIME); + _hardfork_times.emplace_back(HARDFORK_5050_1_TIME); + _hardfork_times.emplace_back(HARDFORK_CORE_429_TIME); + _hardfork_times.emplace_back(HARDFORK_GPOS_TIME); + _hardfork_times.emplace_back(HARDFORK_NFT_TIME); + _hardfork_times.emplace_back(HARDFORK_SON_FOR_HIVE_TIME); + _hardfork_times.emplace_back(HARDFORK_SON_TIME); + _hardfork_times.emplace_back(HARDFORK_SON2_TIME); + _hardfork_times.emplace_back(HARDFORK_SON_FOR_ETHEREUM_TIME); + _hardfork_times.emplace_back(HARDFORK_SWEEPS_TIME); + + std::sort(_hardfork_times.begin(), _hardfork_times.end()); +} + void database::initialize_indexes() { reset_indexes(); - _undo_db.set_max_size( GRAPHENE_MIN_UNDO_HISTORY ); + + const std::lock_guard undo_db_lock{_undo_db_mutex}; + _undo_db.set_max_size(GRAPHENE_MIN_UNDO_HISTORY); //Protocol object indexes add_index< primary_index >(); // 8192 assets per chunk @@ -432,7 +476,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) FC_ASSERT(genesis_state.initial_active_witnesses <= genesis_state.initial_witness_candidates.size(), "initial_active_witnesses is larger than the number of candidate witnesses."); + const std::lock_guard undo_db_lock{_undo_db_mutex}; _undo_db.disable(); + struct auth_inhibitor { auth_inhibitor(database& db) : db(db), old_flags(db.node_properties().skip_flags) { db.node_properties().skip_flags |= skip_authority_check; } @@ -1058,8 +1104,9 @@ void database::init_genesis(const genesis_state_type& genesis_state) FC_ASSERT( _p_witness_schedule_obj->id == witness_schedule_id_type() ); // Initialize witness schedule + #ifndef NDEBUG - const son_schedule_object& sso = + const son_schedule_object& ssobitcoin = #endif create([&](son_schedule_object& _sso) { @@ -1068,24 +1115,64 @@ void database::init_genesis(const genesis_state_type& genesis_state) witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); - auto init_witnesses = get_global_properties().active_witnesses; + auto init_bitcoin_sons = get_global_properties().active_sons.at(sidechain_type::bitcoin); _sso.scheduler = son_scheduler(); - _sso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1); - + _sso.scheduler._min_token_count = std::max(int(init_bitcoin_sons.size()) / 2, 1); _sso.last_scheduling_block = 0; _sso.recent_slots_filled = fc::uint128::max_value(); }); - assert( sso.id == son_schedule_id_type() ); + assert( ssobitcoin.id == son_schedule_id_type(get_son_schedule_id(sidechain_type::bitcoin)) ); + +#ifndef NDEBUG + const son_schedule_object& ssoethereum = +#endif + create([&](son_schedule_object& _sso) + { + // for scheduled + memset(_sso.rng_seed.begin(), 0, _sso.rng_seed.size()); + + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_ethereum_sons = get_global_properties().active_sons.at(sidechain_type::ethereum); + + _sso.scheduler = son_scheduler(); + _sso.scheduler._min_token_count = std::max(int(init_ethereum_sons.size()) / 2, 1); + + _sso.last_scheduling_block = 0; + + _sso.recent_slots_filled = fc::uint128::max_value(); + }); + assert( ssoethereum.id == son_schedule_id_type(get_son_schedule_id(sidechain_type::ethereum)) ); + +#ifndef NDEBUG + const son_schedule_object& ssohive = +#endif + create([&](son_schedule_object& _sso) + { + // for scheduled + memset(_sso.rng_seed.begin(), 0, _sso.rng_seed.size()); + + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_hive_sons = get_global_properties().active_sons.at(sidechain_type::hive); + + _sso.scheduler = son_scheduler(); + _sso.scheduler._min_token_count = std::max(int(init_hive_sons.size()) / 2, 1); + + _sso.last_scheduling_block = 0; + + _sso.recent_slots_filled = fc::uint128::max_value(); + }); + assert( ssohive.id == son_schedule_id_type(get_son_schedule_id(sidechain_type::hive)) ); // Enable fees modify(get_global_properties(), [&genesis_state](global_property_object& p) { p.parameters.current_fees = genesis_state.initial_parameters.current_fees; }); - // Create FBA counters create([&]( fba_accumulator_object& acc ) { diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index 0c6843d9..eaa50d51 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -78,25 +78,34 @@ vector> database::sort } template<> -vector> database::sort_votable_objects(size_t count) const +vector> database::sort_votable_objects(sidechain_type sidechain, size_t count) const { const auto& all_sons = get_index_type().indices().get< by_id >(); std::vector> refs; for( auto& son : all_sons ) { - if(son.has_valid_config(head_block_time()) && son.status != son_status::deregistered) + if(son.has_valid_config(head_block_time(), sidechain) && son.statuses.at(sidechain) != son_status::deregistered) { refs.push_back(std::cref(son)); } } count = std::min(count, refs.size()); std::partial_sort(refs.begin(), refs.begin() + count, refs.end(), - [this](const son_object& a, const son_object& b)->bool { - share_type oa_vote = _vote_tally_buffer[a.vote_id]; - share_type ob_vote = _vote_tally_buffer[b.vote_id]; - if( oa_vote != ob_vote ) - return oa_vote > ob_vote; - return a.vote_id < b.vote_id; + [this, sidechain](const son_object& a, const son_object& b)->bool { + FC_ASSERT(sidechain == sidechain_type::bitcoin || + sidechain == sidechain_type::ethereum || + sidechain == sidechain_type::hive, + "Unexpected sidechain type"); + + FC_ASSERT(a.get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", a)); + FC_ASSERT(b.get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", b)); + const share_type oa_vote = _vote_tally_buffer.size() > *a.get_sidechain_vote_id(sidechain) ? _vote_tally_buffer[*a.get_sidechain_vote_id(sidechain)] : 0; + const share_type ob_vote = _vote_tally_buffer.size() > *b.get_sidechain_vote_id(sidechain) ? _vote_tally_buffer[*b.get_sidechain_vote_id(sidechain)] : 0; + + if( oa_vote != ob_vote ) + return oa_vote > ob_vote; + + return a.get_sidechain_vote_id(sidechain) < b.get_sidechain_vote_id(sidechain); }); refs.resize(count, refs.front()); @@ -176,63 +185,73 @@ void database::update_worker_votes() } } -void database::pay_sons() +void database::pay_sons_before_hf_ethereum() { - time_point_sec now = head_block_time(); + const auto now = head_block_time(); const dynamic_global_property_object& dpo = get_dynamic_global_properties(); // Current requirement is that we have to pay every 24 hours, so the following check - if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { - auto sons = sort_votable_objects(get_global_properties().parameters.maximum_son_count()); + if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) + { + const sidechain_type st = sidechain_type::bitcoin; + const auto sons = sort_votable_objects(st, get_global_properties().parameters.maximum_son_count()); + // After SON2 HF uint64_t total_votes = 0; for( const son_object& son : sons ) { - total_votes += _vote_tally_buffer[son.vote_id]; + FC_ASSERT(son.get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", son)); + total_votes += _vote_tally_buffer[*son.get_sidechain_vote_id(st)]; } - int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + const int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); auto get_weight = [&bits_to_drop]( uint64_t son_votes ) { - uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); + const uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); return weight; }; + // Before SON2 HF auto get_weight_before_son2_hf = []( uint64_t son_votes ) { - int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(son_votes)) - 15, 0); - uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); + const int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(son_votes)) - 15, 0); + const uint16_t weight = std::max((son_votes >> bits_to_drop), uint64_t(1) ); return weight; }; + uint64_t weighted_total_txs_signed = 0; - share_type son_budget = dpo.son_budget; - get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight, &now, &get_weight_before_son2_hf](const object& o) { + const share_type son_budget = dpo.son_budget; + get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight, &now, &get_weight_before_son2_hf, &st](const object& o) { const son_statistics_object& s = static_cast(o); const auto& idx = get_index_type().indices().get(); - auto son_obj = idx.find( s.owner ); - auto son_weight = get_weight(_vote_tally_buffer[son_obj->vote_id]); - if( now < HARDFORK_SON2_TIME ) { - son_weight = get_weight_before_son2_hf(_vote_tally_buffer[son_obj->vote_id]); + const auto son_obj = idx.find( s.owner ); + uint16_t son_weight = 0; + FC_ASSERT(son_obj->get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", *son_obj)); + if( now >= HARDFORK_SON2_TIME ) { + son_weight += get_weight(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } - uint64_t txs_signed = 0; - for (const auto &ts : s.txs_signed) { - txs_signed = txs_signed + ts.second; + else { + son_weight += get_weight_before_son2_hf(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } - weighted_total_txs_signed += (txs_signed * son_weight); + const uint64_t txs_signed_bitcoin = s.txs_signed.contains(sidechain_type::bitcoin) ? s.txs_signed.at(sidechain_type::bitcoin) : 0; + const uint64_t txs_signed_hive = s.txs_signed.contains(sidechain_type::hive) ? s.txs_signed.at(sidechain_type::hive) : 0; + weighted_total_txs_signed += ((txs_signed_bitcoin + txs_signed_hive) * son_weight); }); // Now pay off each SON proportional to the number of transactions signed. - get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &dpo, &son_budget, &get_weight, &get_weight_before_son2_hf, &now](const object& o) { + get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &dpo, &son_budget, &get_weight, &get_weight_before_son2_hf, &now, &st](const object& o) { const son_statistics_object& s = static_cast(o); - uint64_t txs_signed = 0; - for (const auto &ts : s.txs_signed) { - txs_signed = txs_signed + ts.second; - } + const uint64_t txs_signed_bitcoin = s.txs_signed.contains(sidechain_type::bitcoin) ? s.txs_signed.at(sidechain_type::bitcoin) : 0; + const uint64_t txs_signed_hive = s.txs_signed.contains(sidechain_type::hive) ? s.txs_signed.at(sidechain_type::hive) : 0; - if(txs_signed > 0){ + if(txs_signed_bitcoin > 0 || txs_signed_hive > 0) { const auto& idx = get_index_type().indices().get(); auto son_obj = idx.find( s.owner ); - auto son_weight = get_weight(_vote_tally_buffer[son_obj->vote_id]); - if( now < HARDFORK_SON2_TIME ) { - son_weight = get_weight_before_son2_hf(_vote_tally_buffer[son_obj->vote_id]); + uint16_t son_weight = 0; + FC_ASSERT(son_obj->get_sidechain_vote_id(st).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", st)("son", *son_obj)); + if( now >= HARDFORK_SON2_TIME ) { + son_weight += get_weight(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); } - share_type pay = (txs_signed * son_weight * son_budget.value)/weighted_total_txs_signed; + else { + son_weight += get_weight_before_son2_hf(_vote_tally_buffer[*son_obj->get_sidechain_vote_id(st)]); + } + const share_type pay = ((txs_signed_bitcoin + txs_signed_hive) * son_weight * son_budget.value)/weighted_total_txs_signed; modify( *son_obj, [&]( son_object& _son_obj) { _son_obj.pay_son_fee(pay, *this); @@ -245,12 +264,14 @@ void database::pay_sons() //Reset the tx counter in each son statistics object modify( s, [&]( son_statistics_object& _s) { - for (const auto &ts : s.txs_signed) { - _s.txs_signed.at(ts.first) = 0; - } + if(_s.txs_signed.contains(sidechain_type::bitcoin)) + _s.txs_signed.at(sidechain_type::bitcoin) = 0; + if(_s.txs_signed.contains(sidechain_type::hive)) + _s.txs_signed.at(sidechain_type::hive) = 0; }); } }); + //Note the last son pay out time modify( dpo, [&]( dynamic_global_property_object& _dpo ) { @@ -259,122 +280,228 @@ void database::pay_sons() } } -void database::update_son_metrics(const vector& curr_active_sons) +void database::pay_sons_after_hf_ethereum() { - vector current_sons; - - current_sons.reserve(curr_active_sons.size()); - std::transform(curr_active_sons.begin(), curr_active_sons.end(), - std::inserter(current_sons, current_sons.end()), - [](const son_info &swi) { - return swi.son_id; - }); - - const auto& son_idx = get_index_type().indices().get< by_id >(); - for( auto& son : son_idx ) + const time_point_sec now = head_block_time(); + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + // Current requirement is that we have to pay every 24 hours, so the following check + if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { - auto& stats = son.statistics(*this); - bool is_active_son = (std::find(current_sons.begin(), current_sons.end(), son.id) != current_sons.end()); - modify( stats, [&]( son_statistics_object& _stats ) + flat_map bits_to_drop; + for(const auto& active_sidechain_type : active_sidechain_types(now)) { - if(is_active_son) { - _stats.total_voted_time = _stats.total_voted_time + get_global_properties().parameters.maintenance_interval; + assert( _son_count_histogram_buffer.at(active_sidechain_type).size() > 0 ); + const share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer.at(active_sidechain_type)[0]) / 2; + /// accounts that vote for 0 or 1 son do not get to express an opinion on + /// the number of sons to have (they abstain and are non-voting accounts) + share_type stake_tally = 0; + size_t son_count = 0; + if( stake_target > 0 ) + { + while( (son_count < _son_count_histogram_buffer.at(active_sidechain_type).size() - 1) + && (stake_tally <= stake_target) ) + { + stake_tally += _son_count_histogram_buffer.at(active_sidechain_type)[++son_count]; + } } - _stats.total_downtime += _stats.current_interval_downtime; - _stats.current_interval_downtime = 0; - for (const auto &str : _stats.sidechain_txs_reported) { - _stats.sidechain_txs_reported.at(str.first) = 0; + + const auto sons = sort_votable_objects(active_sidechain_type, (std::max(son_count*2+1, (size_t)get_chain_properties().immutable_parameters.min_son_count))); + + // After SON2 HF + uint64_t total_votes = 0; + for( const son_object& son : sons ) + { + FC_ASSERT(son.get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", son)); + total_votes += _vote_tally_buffer[*son.get_sidechain_vote_id(active_sidechain_type)]; } + bits_to_drop[active_sidechain_type] = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + } + + auto get_weight = [&bits_to_drop]( sidechain_type sidechain, uint64_t son_votes ) { + const uint16_t weight = std::max((son_votes >> bits_to_drop.at(sidechain)), uint64_t(1) ); + return weight; + }; + + // Calculate weighted_total_txs_signed + uint64_t weighted_total_txs_signed = 0; + get_index_type().inspect_all_objects([this, &weighted_total_txs_signed, &get_weight, &now](const object& o) { + for(const auto& active_sidechain_type : active_sidechain_types(now)) { + const son_statistics_object &s = static_cast(o); + const auto &idx = get_index_type().indices().get(); + const auto son_obj = idx.find(s.owner); + FC_ASSERT(son_obj->get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", *son_obj)); + const uint16_t son_weight = get_weight(active_sidechain_type, _vote_tally_buffer[*son_obj->get_sidechain_vote_id(active_sidechain_type)]); + const uint64_t txs_signed = s.txs_signed.contains(active_sidechain_type) ? s.txs_signed.at(active_sidechain_type) : 0; + weighted_total_txs_signed += (txs_signed * son_weight); + } + }); + + // Now pay off each SON proportional to the number of transactions signed + const share_type son_budget = dpo.son_budget; + get_index_type().inspect_all_objects([this, &now, &get_weight, &weighted_total_txs_signed, &dpo, &son_budget](const object& o) { + for(const auto& active_sidechain_type : active_sidechain_types(now)) { + const son_statistics_object &s = static_cast(o); + + const uint64_t txs_signed = s.txs_signed.contains(active_sidechain_type) ? s.txs_signed.at(active_sidechain_type) : 0; + if (txs_signed > 0) { + const auto &idx = get_index_type().indices().get(); + auto son_obj = idx.find(s.owner); + uint16_t son_weight = 0; + FC_ASSERT(son_obj->get_sidechain_vote_id(active_sidechain_type).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", active_sidechain_type)("son", *son_obj)); + son_weight += get_weight(active_sidechain_type, _vote_tally_buffer[*son_obj->get_sidechain_vote_id(active_sidechain_type)]); + const share_type pay = (txs_signed * son_weight * son_budget.value) / weighted_total_txs_signed; + modify(*son_obj, [&](son_object &_son_obj) { + _son_obj.pay_son_fee(pay, *this); + }); + // Remove the amount paid out to SON from global SON Budget + modify(dpo, [&](dynamic_global_property_object &_dpo) { + _dpo.son_budget -= pay; + }); + // Reset the tx counter in each son statistics object + modify(s, [&](son_statistics_object &_s) { + if (_s.txs_signed.contains(active_sidechain_type)) + _s.txs_signed.at(active_sidechain_type) = 0; + }); + } + } + }); + + //Note the last son pay out time + modify( dpo, [&]( dynamic_global_property_object& _dpo ) + { + _dpo.last_son_payout_time = now; }); } } -void database::update_son_statuses(const vector& curr_active_sons, const vector& new_active_sons) +void database::update_son_metrics(const flat_map >& curr_active_sons) { - vector current_sons, new_sons; - vector sons_to_remove, sons_to_add; - const auto& idx = get_index_type().indices().get(); + for(const auto& curr_active_sidechain_sons : curr_active_sons) { + const auto& sidechain = curr_active_sidechain_sons.first; + const auto& _curr_active_sidechain_sons = curr_active_sidechain_sons.second; - current_sons.reserve(curr_active_sons.size()); - std::transform(curr_active_sons.begin(), curr_active_sons.end(), - std::inserter(current_sons, current_sons.end()), - [](const son_info &swi) { - return swi.son_id; - }); + vector current_sons; - new_sons.reserve(new_active_sons.size()); - std::transform(new_active_sons.begin(), new_active_sons.end(), - std::inserter(new_sons, new_sons.end()), - [](const son_info &swi) { - return swi.son_id; - }); + current_sons.reserve(_curr_active_sidechain_sons.size()); + std::transform(_curr_active_sidechain_sons.cbegin(), _curr_active_sidechain_sons.cend(), + std::inserter(current_sons, current_sons.end()), + [](const son_sidechain_info &swi) { + return swi.son_id; + }); - // find all cur_active_sons members that is not in new_active_sons - for_each(current_sons.begin(), current_sons.end(), - [&sons_to_remove, &new_sons](const son_id_type& si) - { - if(std::find(new_sons.begin(), new_sons.end(), si) == - new_sons.end()) - { - sons_to_remove.push_back(si); - } + const auto &son_idx = get_index_type().indices().get(); + for (auto &son : son_idx) { + auto &stats = son.statistics(*this); + bool is_active_son = (std::find(current_sons.begin(), current_sons.end(), son.id) != current_sons.end()); + modify(stats, [&](son_statistics_object &_stats) { + if (is_active_son) { + _stats.total_voted_time[sidechain] = _stats.total_voted_time[sidechain] + get_global_properties().parameters.maintenance_interval; } - ); + if(!_stats.current_interval_downtime.contains(sidechain)) + _stats.current_interval_downtime[sidechain] = 0; - for( const auto& sid : sons_to_remove ) - { - auto son = idx.find( sid ); - if(son == idx.end()) // SON is deleted already - continue; - // keep maintenance status for nodes becoming inactive - if(son->status == son_status::active) - { - modify( *son, [&]( son_object& obj ){ - obj.status = son_status::inactive; - }); + _stats.total_downtime[sidechain] += _stats.current_interval_downtime.at(sidechain); + _stats.current_interval_downtime[sidechain] = 0; + _stats.sidechain_txs_reported[sidechain] = 0; + }); } } +} - // find all new_active_sons members that is not in cur_active_sons - for_each(new_sons.begin(), new_sons.end(), - [&sons_to_add, ¤t_sons](const son_id_type& si) - { - if(std::find(current_sons.begin(), current_sons.end(), si) == - current_sons.end()) - { - sons_to_add.push_back(si); - } - } - ); +void database::update_son_statuses( const flat_map >& curr_active_sons, + const flat_map >& new_active_sons ) +{ + for(const auto& new_active_sidechain_sons : new_active_sons) { + const auto& sidechain = new_active_sidechain_sons.first; - for( const auto& sid : sons_to_add ) - { - auto son = idx.find( sid ); - FC_ASSERT(son != idx.end(), "Invalid SON in active list, id={sonid}.", ("sonid", sid)); - // keep maintenance status for new nodes - if(son->status == son_status::inactive) - { - modify( *son, [&]( son_object& obj ){ - obj.status = son_status::active; - }); + vector current_sons, new_sons; + vector sons_to_remove, sons_to_add; + const auto &idx = get_index_type().indices().get(); + + if(curr_active_sons.contains(sidechain)) { + current_sons.reserve(curr_active_sons.at(sidechain).size()); + std::transform(curr_active_sons.at(sidechain).cbegin(), curr_active_sons.at(sidechain).cend(), + std::inserter(current_sons, current_sons.end()), + [](const son_sidechain_info &swi) { + return swi.son_id; + }); } - } - ilog("New SONS"); - for(size_t i = 0; i < new_sons.size(); i++) { - auto son = idx.find( new_sons[i] ); - if(son == idx.end()) // SON is deleted already + new_sons.reserve(new_active_sons.at(sidechain).size()); + std::transform(new_active_sons.at(sidechain).cbegin(), new_active_sons.at(sidechain).cend(), + std::inserter(new_sons, new_sons.end()), + [](const son_sidechain_info &swi) { + return swi.son_id; + }); + + // find all cur_active_sons members that is not in new_active_sons + for_each(current_sons.begin(), current_sons.end(), + [&sons_to_remove, &new_sons](const son_id_type &si) { + if (std::find(new_sons.begin(), new_sons.end(), si) == + new_sons.end()) { + sons_to_remove.push_back(si); + } + }); + + for (const auto &sid : sons_to_remove) { + auto son = idx.find(sid); + if (son == idx.end()) // SON is deleted already continue; - ilog( "${s}, status = ${ss}, total_votes = ${sv}", ("s", new_sons[i])("ss", son->status)("sv", son->total_votes) ); + // keep maintenance status for nodes becoming inactive + if (son->statuses.at(sidechain) == son_status::active) { + modify(*son, [&](son_object &obj) { + obj.statuses.at(sidechain) = son_status::inactive; + }); + } + } + + // find all new_active_sons members that is not in cur_active_sons + for_each(new_sons.begin(), new_sons.end(), + [&sons_to_add, ¤t_sons](const son_id_type &si) { + if (std::find(current_sons.begin(), current_sons.end(), si) == + current_sons.end()) { + sons_to_add.push_back(si); + } + }); + + for (const auto &sid : sons_to_add) { + auto son = idx.find(sid); + FC_ASSERT(son != idx.end(), "Invalid SON in active list, id = ${sonid}.", ("sonid", sid)); + // keep maintenance status for new nodes + if (son->statuses.at(sidechain) == son_status::inactive) { + modify(*son, [&](son_object &obj) { + obj.statuses.at(sidechain) = son_status::active; + }); + } + } + + ilog("New SONS for sidechain = ${sidechain}", ("sidechain", sidechain)); + for (size_t i = 0; i < new_sons.size(); i++) { + auto son = idx.find(new_sons[i]); + if (son == idx.end()) // SON is deleted already + continue; + ilog("${s}, status = ${ss}, total_votes = ${sv}", ("s", new_sons[i])("ss", son->statuses.at(sidechain))("sv", son->total_votes)); + } } - if( sons_to_remove.size() > 0 ) - { + //! Remove inactive sons (when all sidechain inactive) + vector sons_to_remove; + const auto &idx = get_index_type().indices().get(); + for(const auto& son : idx) { + bool inactive_son = true; + for(const auto& status : son.statuses) { + if (status.second != son_status::inactive) + inactive_son = false; + } + if (inactive_son) + sons_to_remove.emplace_back(son.id); + } + if (sons_to_remove.size() > 0) { remove_inactive_son_proposals(sons_to_remove); } } -void database::update_son_wallet(const vector& new_active_sons) +void database::update_son_wallet(const flat_map >& new_active_sons) { bool should_recreate_pw = true; @@ -387,8 +514,16 @@ void database::update_son_wallet(const vector& new_active_sons) bool wallet_son_sets_equal = (cur_wallet_sons.size() == new_active_sons.size()); if (wallet_son_sets_equal) { - for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { - wallet_son_sets_equal = wallet_son_sets_equal && cur_wallet_sons.at(i) == new_active_sons.at(i); + for( const auto& cur_wallet_sidechain_sons : cur_wallet_sons ) { + const auto& sidechain = cur_wallet_sidechain_sons.first; + const auto& _cur_wallet_sidechain_sons = cur_wallet_sidechain_sons.second; + + wallet_son_sets_equal = wallet_son_sets_equal && (_cur_wallet_sidechain_sons.size() == new_active_sons.at(sidechain).size()); + if (wallet_son_sets_equal) { + for (size_t i = 0; i < _cur_wallet_sidechain_sons.size(); i++) { + wallet_son_sets_equal = wallet_son_sets_equal && (_cur_wallet_sidechain_sons.at(i) == new_active_sons.at(sidechain).at(i)); + } + } } } @@ -401,14 +536,24 @@ void database::update_son_wallet(const vector& new_active_sons) } } - should_recreate_pw = should_recreate_pw && (new_active_sons.size() >= get_chain_properties().immutable_parameters.min_son_count); + bool should_recreate_pw_sidechain = false; + for(const auto& new_active_sidechain_sons : new_active_sons) { + if(new_active_sidechain_sons.second.size() >= get_chain_properties().immutable_parameters.min_son_count) + should_recreate_pw_sidechain = true; + } + should_recreate_pw = should_recreate_pw && should_recreate_pw_sidechain; if (should_recreate_pw) { // Create new son_wallet_object, to initiate wallet recreation create( [&]( son_wallet_object& obj ) { obj.valid_from = head_block_time(); obj.expires = time_point_sec::maximum(); - obj.sons.insert(obj.sons.end(), new_active_sons.begin(), new_active_sons.end()); + for(const auto& new_active_sidechain_sons : new_active_sons){ + const auto& sidechain = new_active_sidechain_sons.first; + const auto& _new_active_sidechain_sons = new_active_sidechain_sons.second; + + obj.sons[sidechain].insert(obj.sons[sidechain].end(), _new_active_sidechain_sons.cbegin(), _new_active_sidechain_sons.cend()); + } }); } } @@ -665,44 +810,71 @@ void database::update_active_sons() } assert( _son_count_histogram_buffer.size() > 0 ); - share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer[0]) / 2; +#ifndef NDEBUG + for( const auto& son_count_histogram_buffer : _son_count_histogram_buffer ){ + assert( son_count_histogram_buffer.second.size() > 0 ); + } +#endif - /// accounts that vote for 0 or 1 son do not get to express an opinion on - /// the number of sons to have (they abstain and are non-voting accounts) - - share_type stake_tally = 0; - - size_t son_count = 0; - if( stake_target > 0 ) + const auto supported_active_sidechain_types = active_sidechain_types(head_block_time()); + flat_map son_count; + for(const auto& active_sidechain_type : supported_active_sidechain_types) { - while( (son_count < _son_count_histogram_buffer.size() - 1) - && (stake_tally <= stake_target) ) + const share_type stake_target = (_total_voting_stake-_son_count_histogram_buffer.at(active_sidechain_type)[0]) / 2; + + /// accounts that vote for 0 or 1 son do not get to express an opinion on + /// the number of sons to have (they abstain and are non-voting accounts) + share_type stake_tally = 0; + son_count[active_sidechain_type] = 0; + if( stake_target > 0 ) { - stake_tally += _son_count_histogram_buffer[++son_count]; + while( (son_count.at(active_sidechain_type) < _son_count_histogram_buffer.at(active_sidechain_type).size() - 1) + && (stake_tally <= stake_target) ) + { + stake_tally += _son_count_histogram_buffer.at(active_sidechain_type)[ ++son_count[active_sidechain_type] ]; + } } } const global_property_object& gpo = get_global_properties(); - const chain_parameters& cp = gpo.parameters; - auto sons = sort_votable_objects(cp.maximum_son_count()); - + const chain_property_object& cpo = get_chain_properties(); const auto& all_sons = get_index_type().indices(); + flat_map > > sons; + for(const auto& active_sidechain_type : supported_active_sidechain_types) + { + if(head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) { + sons[active_sidechain_type] = sort_votable_objects(active_sidechain_type, + (std::max(son_count.at(active_sidechain_type) * 2 + 1, (size_t)cpo.immutable_parameters.min_son_count))); + } + else { + sons[active_sidechain_type] = sort_votable_objects(sidechain_type::bitcoin, get_global_properties().parameters.maximum_son_count()); + } + } - auto& local_vote_buffer_ref = _vote_tally_buffer; for( const son_object& son : all_sons ) { - if(son.status == son_status::request_maintenance) + for(const auto& status: son.statuses) { - auto& stats = son.statistics(*this); - modify( stats, [&]( son_statistics_object& _s){ - _s.last_down_timestamp = head_block_time(); + const auto& sidechain = status.first; + if(status.second == son_status::in_maintenance) + { + auto &stats = son.statistics(*this); + modify(stats, [&](son_statistics_object &_s) { + _s.last_down_timestamp[sidechain] = head_block_time(); }); + } } - modify( son, [local_vote_buffer_ref]( son_object& obj ){ - obj.total_votes = local_vote_buffer_ref[obj.vote_id]; - if(obj.status == son_status::request_maintenance) - obj.status = son_status::in_maintenance; - }); + + modify( son, [this]( son_object& obj ){ + for(const auto& sidechain_vote_id : obj.sidechain_vote_ids ){ + obj.total_votes[sidechain_vote_id.first] = _vote_tally_buffer.size() > sidechain_vote_id.second ? _vote_tally_buffer[sidechain_vote_id.second] : 0; + } + for(auto& status: obj.statuses) + { + if (status.second == son_status::request_maintenance) + status.second = son_status::in_maintenance; + } + }); } // Update SON authority @@ -710,21 +882,24 @@ void database::update_active_sons() { modify( get(gpo.parameters.son_account()), [&]( account_object& a ) { + set account_ids; + for(const auto& sidechain_sons : sons) + { + for( const son_object& son : sidechain_sons.second ) + { + account_ids.emplace(son.son_account); + } + } + if( head_block_time() < HARDFORK_533_TIME ) { - map weights; a.active.weight_threshold = 0; a.active.account_auths.clear(); - for( const son_object& son : sons ) - { - weights.emplace(son.son_account, uint64_t(1)); - } - - for( const auto& weight : weights ) + for( const auto& account_id : account_ids ) { // Ensure that everyone has at least one vote. Zero weights aren't allowed. - a.active.account_auths[weight.first] += 1; + a.active.account_auths[account_id] += 1; a.active.weight_threshold += 1; } @@ -735,8 +910,10 @@ void database::update_active_sons() else { vote_counter vc; - for( const son_object& son : sons ) - vc.add( son.son_account, UINT64_C(1) ); + for( const auto& account_id : account_ids ) + { + vc.add(account_id, UINT64_C(1)); + } vc.finish_2_3( a.active ); } } ); @@ -744,22 +921,37 @@ void database::update_active_sons() // Compare current and to-be lists of active sons - auto cur_active_sons = gpo.active_sons; - vector new_active_sons; + const auto cur_active_sons = gpo.active_sons; + flat_map > new_active_sons; const auto &acc = get(gpo.parameters.son_account()); - for( const son_object& son : sons ) { - son_info swi; - swi.son_id = son.id; - swi.weight = acc.active.account_auths.at(son.son_account); - swi.signing_key = son.signing_key; - swi.sidechain_public_keys = son.sidechain_public_keys; - new_active_sons.push_back(swi); + for( const auto& sidechain_sons : sons ){ + const auto& sidechain = sidechain_sons.first; + const auto& sons_array = sidechain_sons.second; + + new_active_sons[sidechain].reserve(sons_array.size()); + for( const son_object& son : sons_array ) { + son_sidechain_info swi; + swi.son_id = son.id; + swi.weight = acc.active.account_auths.at(son.son_account); + swi.signing_key = son.signing_key; + if (son.sidechain_public_keys.find(sidechain) != son.sidechain_public_keys.end()) + swi.public_key = son.sidechain_public_keys.at(sidechain); + new_active_sons[sidechain].push_back(swi); + } } bool son_sets_equal = (cur_active_sons.size() == new_active_sons.size()); if (son_sets_equal) { - for( size_t i = 0; i < cur_active_sons.size(); i++ ) { - son_sets_equal = son_sets_equal && cur_active_sons.at(i) == new_active_sons.at(i); + for( const auto& cur_active_sidechain_sons : cur_active_sons ){ + const auto& sidechain = cur_active_sidechain_sons.first; + const auto& _cur_active_sidechain_sons = cur_active_sidechain_sons.second; + + son_sets_equal = son_sets_equal && (_cur_active_sidechain_sons.size() == new_active_sons.at(sidechain).size()); + if (son_sets_equal) { + for (size_t i = 0; i < _cur_active_sidechain_sons.size(); i++) { + son_sets_equal = son_sets_equal && (_cur_active_sidechain_sons.at(i) == new_active_sons.at(sidechain).at(i)); + } + } } } @@ -774,28 +966,38 @@ void database::update_active_sons() modify(gpo, [&]( global_property_object& gp ){ gp.active_sons.clear(); gp.active_sons.reserve(new_active_sons.size()); - gp.active_sons.insert(gp.active_sons.end(), new_active_sons.begin(), new_active_sons.end()); - }); + for( const auto& new_active_sidechain_sons : new_active_sons ) { + const auto& sidechain = new_active_sidechain_sons.first; + const auto& _new_active_sidechain_sons = new_active_sidechain_sons.second; - const son_schedule_object& sso = son_schedule_id_type()(*this); - modify(sso, [&](son_schedule_object& _sso) - { - flat_set active_sons; - active_sons.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), - std::inserter(active_sons, active_sons.end()), - [](const son_info& swi) { - return swi.son_id; - }); - _sso.scheduler.update(active_sons); - // similar to witness, produce schedule for sons - if(cur_active_sons.size() == 0 && new_active_sons.size() > 0) - { - witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); - for( size_t i=0; i active_sons; + active_sons.reserve(gpo.active_sons.at(active_sidechain_type).size()); + std::transform(gpo.active_sons.at(active_sidechain_type).cbegin(), gpo.active_sons.at(active_sidechain_type).cend(), + std::inserter(active_sons, active_sons.end()), + [](const son_sidechain_info& swi) { + return swi.son_id; + }); + _sso.scheduler.update(active_sons); + // similar to witness, produce schedule for sons + if( ((cur_active_sons.contains(active_sidechain_type) && cur_active_sons.at(active_sidechain_type).size() == 0) || + !cur_active_sons.contains(active_sidechain_type)) && new_active_sons.at(active_sidechain_type).size() > 0 ) + { + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + for( size_t i=0; i= HARDFORK_GPOS_TIME) { - auto gpo = db.get_global_properties(); + const auto gpo = db.get_global_properties(); auto period_start = db.get_global_properties().parameters.gpos_period_start(); - auto vesting_period = db.get_global_properties().parameters.gpos_period(); + const auto vesting_period = db.get_global_properties().parameters.gpos_period(); - auto now = db.head_block_time(); - if(now.sec_since_epoch() >= (period_start + vesting_period)) + const auto now = db.head_block_time(); + while(now.sec_since_epoch() >= (period_start + vesting_period)) { // roll db.modify(db.get_global_properties(), [period_start, vesting_period](global_property_object& p) { p.parameters.extensions.value.gpos_period_start = period_start + vesting_period; }); + + period_start = db.get_global_properties().parameters.gpos_period_start(); } } } @@ -1921,7 +2125,7 @@ void database::perform_son_tasks() }); } // create BTC asset here because son_account is the issuer of the BTC - if (gpo.parameters.btc_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_TIME) + if (gpo.parameters.btc_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { @@ -1940,7 +2144,7 @@ void database::perform_son_tasks() asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); - a.options.core_exchange_rate.quote.amount = 2500; // CoinMarketCap approx value + a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set @@ -1954,8 +2158,42 @@ void database::perform_son_tasks() gpo.pending_parameters->extensions.value.btc_asset = btc_asset.get_id(); }); } + // create ETH asset here because son_account is the issuer of the ETH + if (gpo.parameters.eth_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) + { + const asset_dynamic_data_object& dyn_asset = + create([](asset_dynamic_data_object& a) { + a.current_supply = 0; + }); + + const asset_object& eth_asset = + create( [&gpo, &dyn_asset]( asset_object& a ) { + a.symbol = "ETH"; + a.precision = 8; + a.issuer = gpo.parameters.son_account(); + a.options.max_supply = GRAPHENE_MAX_SHARE_SUPPLY; + a.options.market_fee_percent = 500; // 5% + a.options.issuer_permissions = UIA_ASSET_ISSUER_PERMISSION_MASK; + a.options.flags = asset_issuer_permission_flags::charge_market_fee | + asset_issuer_permission_flags::override_authority; + a.options.core_exchange_rate.base.amount = 100000; + a.options.core_exchange_rate.base.asset_id = asset_id_type(0); + a.options.core_exchange_rate.quote.amount = 2500; + a.options.core_exchange_rate.quote.asset_id = a.id; + a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty + a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set + a.options.whitelist_markets.clear(); // might be traded with + a.options.blacklist_markets.clear(); // might not be traded with + a.dynamic_asset_data_id = dyn_asset.id; + }); + modify( gpo, [ð_asset]( global_property_object& gpo ) { + gpo.parameters.extensions.value.eth_asset = eth_asset.get_id(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.eth_asset = eth_asset.get_id(); + }); + } // create HBD asset here because son_account is the issuer of the HBD - if (gpo.parameters.hbd_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) + if (gpo.parameters.hbd_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { @@ -1974,7 +2212,7 @@ void database::perform_son_tasks() asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); - a.options.core_exchange_rate.quote.amount = 2500; // CoinMarketCap approx value + a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set @@ -1989,7 +2227,7 @@ void database::perform_son_tasks() }); } // create HIVE asset here because son_account is the issuer of the HIVE - if (gpo.parameters.hive_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) + if (gpo.parameters.hive_asset() == asset_id_type() && head_block_time() >= HARDFORK_SON_FOR_HIVE_TIME) { const asset_dynamic_data_object& dyn_asset = create([](asset_dynamic_data_object& a) { @@ -2008,7 +2246,7 @@ void database::perform_son_tasks() asset_issuer_permission_flags::override_authority; a.options.core_exchange_rate.base.amount = 100000; a.options.core_exchange_rate.base.asset_id = asset_id_type(0); - a.options.core_exchange_rate.quote.amount = 2500; // CoinMarketCap approx value + a.options.core_exchange_rate.quote.amount = 2500; a.options.core_exchange_rate.quote.asset_id = a.id; a.options.whitelist_authorities.clear(); // accounts allowed to use asset, if not empty a.options.blacklist_authorities.clear(); // accounts who can blacklist other accounts to use asset, if white_list flag is set @@ -2022,25 +2260,75 @@ void database::perform_son_tasks() gpo.pending_parameters->extensions.value.hive_asset = hive_asset.get_id(); }); } + // Pay the SONs if (head_block_time() >= HARDFORK_SON_TIME) { // Before making a budget we should pay out SONs // This function should check if its time to pay sons // and modify the global son funds accordingly, whatever is left is passed on to next budget - pay_sons(); + if(head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME) + pay_sons_before_hf_ethereum(); + else + pay_sons_after_hf_ethereum(); + } + + // Split vote_ids + if (head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) { + // Get SON 1.33.0 and check if it has HIVE vote_id + const son_id_type sid = son_id_type(0); + const auto p_son = find(sid); + if(p_son != nullptr) { + if (p_son->sidechain_vote_ids.find(sidechain_type::hive) == p_son->sidechain_vote_ids.end()) { + // Add vote_ids for HIVE and ETHEREUM to all existing SONs + const auto &all_sons = get_index_type().indices().get(); + for (const son_object &son : all_sons) { + const auto existing_vote_id_bitcoin = son.get_bitcoin_vote_id(); + vote_id_type new_vote_id_hive; + vote_id_type new_vote_id_eth; + + modify(gpo, [&new_vote_id_hive, &new_vote_id_eth](global_property_object &p) { + new_vote_id_hive = get_next_vote_id(p, vote_id_type::son_hive); + new_vote_id_eth = get_next_vote_id(p, vote_id_type::son_ethereum); + }); + + modify(son, [new_vote_id_hive, new_vote_id_eth](son_object &obj) { + obj.sidechain_vote_ids[sidechain_type::hive] = new_vote_id_hive; + obj.sidechain_vote_ids[sidechain_type::ethereum] = new_vote_id_eth; + }); + + // Duplicate all votes from bitcoin to hive + const auto &all_accounts = get_index_type().indices().get(); + for (const auto &account : all_accounts) { + if (existing_vote_id_bitcoin.valid() && account.options.votes.count(*existing_vote_id_bitcoin) != 0) { + modify(account, [new_vote_id_hive](account_object &a) { + a.options.votes.insert(new_vote_id_hive); + }); + } + } + } + } + } } } void update_son_params(database& db) { - if( (db.head_block_time() >= HARDFORK_SON2_TIME) && (db.head_block_time() < HARDFORK_SON3_TIME) ) + if( (db.head_block_time() >= HARDFORK_SON2_TIME) && (db.head_block_time() < HARDFORK_SON_FOR_ETHEREUM_TIME) ) { const auto& gpo = db.get_global_properties(); db.modify( gpo, []( global_property_object& gpo ) { gpo.parameters.extensions.value.maximum_son_count = 7; }); } + + if( (db.head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) ) + { + const auto& gpo = db.get_global_properties(); + db.modify( gpo, []( global_property_object& gpo ) { + gpo.parameters.extensions.value.maximum_son_count = GRAPHENE_DEFAULT_MAX_SONS; + }); + } } void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) @@ -2067,7 +2355,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g d._vote_tally_buffer.resize(props.next_available_vote_id); d._witness_count_histogram_buffer.resize(props.parameters.maximum_witness_count / 2 + 1); d._committee_count_histogram_buffer.resize(props.parameters.maximum_committee_count / 2 + 1); - d._son_count_histogram_buffer.resize(props.parameters.maximum_son_count() / 2 + 1); + for( auto& son_count_histogram_buffer : d._son_count_histogram_buffer ){ + son_count_histogram_buffer.second.resize(props.parameters.maximum_son_count() / 2 + 1); + } d._total_voting_stake = 0; auto balance_type = vesting_balance_type::normal; @@ -2167,17 +2457,24 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g // same rationale as for witnesses d._committee_count_histogram_buffer[offset] += voting_stake; } - if( opinion_account.options.num_son <= props.parameters.maximum_son_count() ) + + if ( opinion_account.options.extensions.value.num_son.valid() ) { - uint16_t offset = std::min(size_t(opinion_account.options.num_son/2), - d._son_count_histogram_buffer.size() - 1); - // votes for a number greater than maximum_son_count - // are turned into votes for maximum_son_count. - // - // in particular, this takes care of the case where a - // member was voting for a high number, then the - // parameter was lowered. - d._son_count_histogram_buffer[offset] += voting_stake; + for(const auto& num_sidechain_son : *opinion_account.options.extensions.value.num_son) { + const auto sidechain = num_sidechain_son.first; + const auto& num_son = num_sidechain_son.second; + if (num_son <= props.parameters.maximum_son_count()) { + uint16_t offset = std::min(size_t(num_son / 2), + d._son_count_histogram_buffer.at(sidechain).size() - 1); + // votes for a number greater than maximum_son_count + // are turned into votes for maximum_son_count. + // + // in particular, this takes care of the case where a + // member was voting for a high number, then the + // parameter was lowered. + d._son_count_histogram_buffer.at(sidechain)[offset] += voting_stake; + } + } } d._total_voting_stake += voting_stake; @@ -2192,10 +2489,20 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g private: vector& target; }; + struct clear_canary_map { + clear_canary_map(flat_map >& target): target(target){} + ~clear_canary_map() { + for(auto& sidechain_target : target){ + sidechain_target.second.clear(); + } + } + private: + flat_map >& target; + }; clear_canary a(_witness_count_histogram_buffer), b(_committee_count_histogram_buffer), - d(_son_count_histogram_buffer), c(_vote_tally_buffer); + clear_canary_map d{_son_count_histogram_buffer}; perform_son_tasks(); update_top_n_authorities(*this); @@ -2267,13 +2574,17 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g p.pending_parameters->extensions.value.hbd_asset = p.parameters.extensions.value.hbd_asset; if( !p.pending_parameters->extensions.value.hive_asset.valid() ) p.pending_parameters->extensions.value.hive_asset = p.parameters.extensions.value.hive_asset; + if( !p.pending_parameters->extensions.value.eth_asset.valid() ) + p.pending_parameters->extensions.value.eth_asset = p.parameters.extensions.value.eth_asset; // the following parameters are not allowed to be changed. So take what is in global property - p.pending_parameters->extensions.value.hive_asset = p.parameters.extensions.value.hive_asset; - p.pending_parameters->extensions.value.hbd_asset = p.parameters.extensions.value.hbd_asset; - p.pending_parameters->extensions.value.btc_asset = p.parameters.extensions.value.btc_asset; - p.pending_parameters->extensions.value.son_account = p.parameters.extensions.value.son_account; p.pending_parameters->extensions.value.gpos_period_start = p.parameters.extensions.value.gpos_period_start; + p.pending_parameters->extensions.value.son_account = p.parameters.extensions.value.son_account; + p.pending_parameters->extensions.value.btc_asset = p.parameters.extensions.value.btc_asset; + p.pending_parameters->extensions.value.maximum_son_count = p.parameters.extensions.value.maximum_son_count; + p.pending_parameters->extensions.value.hbd_asset = p.parameters.extensions.value.hbd_asset; + p.pending_parameters->extensions.value.hive_asset = p.parameters.extensions.value.hive_asset; + p.pending_parameters->extensions.value.eth_asset = p.parameters.extensions.value.eth_asset; p.parameters = std::move(*p.pending_parameters); p.pending_parameters.reset(); diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 2ca49c4c..4a3b519f 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -44,6 +44,7 @@ database::database() : { initialize_indexes(); initialize_evaluators(); + initialize_hardforks(); } database::~database() diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 62d6d2bf..2360a318 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -203,27 +203,10 @@ struct get_impacted_account_visitor _impacted.insert( op.issuer ); } - void operator()( const transfer_to_blind_operation& op ) - { - _impacted.insert( op.from ); - for( const auto& out : op.outputs ) - add_authority_accounts( _impacted, out.owner ); - } - - void operator()( const blind_transfer_operation& op ) - { - for( const auto& in : op.inputs ) - add_authority_accounts( _impacted, in.owner ); - for( const auto& out : op.outputs ) - add_authority_accounts( _impacted, out.owner ); - } - - void operator()( const transfer_from_blind_operation& op ) - { - _impacted.insert( op.to ); - for( const auto& in : op.inputs ) - add_authority_accounts( _impacted, in.owner ); - } + //! We don't use this operations + void operator()( const transfer_to_blind_operation& op ){} + void operator()( const blind_transfer_operation& op ){} + void operator()( const transfer_from_blind_operation& op ){} void operator()( const asset_settle_cancel_operation& op ) { diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index 084c8e1d..d4874b29 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -74,21 +74,32 @@ witness_id_type database::get_scheduled_witness( uint32_t slot_num )const return wid; } -son_id_type database::get_scheduled_son( uint32_t slot_num )const +unsigned_int database::get_son_schedule_id( sidechain_type type )const +{ + static const map schedule_map = { + { sidechain_type::bitcoin, 0 }, + { sidechain_type::ethereum, 1 }, + { sidechain_type::hive, 2 } + }; + + return schedule_map.at(type); +} + +son_id_type database::get_scheduled_son( sidechain_type type, uint32_t slot_num )const { son_id_type sid; const global_property_object& gpo = get_global_properties(); if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) { const dynamic_global_property_object& dpo = get_dynamic_global_properties(); - const son_schedule_object& sso = son_schedule_id_type()(*this); + const son_schedule_object& sso = son_schedule_id_type(get_son_schedule_id(type))(*this); uint64_t current_aslot = dpo.current_aslot + slot_num; return sso.current_shuffled_sons[ current_aslot % sso.current_shuffled_sons.size() ]; } if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM && slot_num != 0 ) { - const son_schedule_object& sso = son_schedule_id_type()(*this); + const son_schedule_object& sso = son_schedule_id_type(get_son_schedule_id(type))(*this); // ask the near scheduler who goes in the given slot bool slot_is_near = sso.scheduler.get_slot(slot_num-1, sid); if(! slot_is_near) @@ -189,36 +200,39 @@ void database::update_witness_schedule() } } -void database::update_son_schedule() +void database::update_son_schedule(sidechain_type type) { - const son_schedule_object& sso = son_schedule_id_type()(*this); const global_property_object& gpo = get_global_properties(); - if( head_block_num() % gpo.active_sons.size() == 0 ) + const son_schedule_object& sidechain_sso = get(son_schedule_id_type(get_son_schedule_id(type))); + if( gpo.active_sons.at(type).size() != 0 && + head_block_num() % gpo.active_sons.at(type).size() == 0) { - modify( sso, [&]( son_schedule_object& _sso ) + modify( sidechain_sso, [&]( son_schedule_object& _sso ) { _sso.current_shuffled_sons.clear(); - _sso.current_shuffled_sons.reserve( gpo.active_sons.size() ); + _sso.current_shuffled_sons.reserve( gpo.active_sons.at(type).size() ); - for( const son_info& w : gpo.active_sons ) - _sso.current_shuffled_sons.push_back( w.son_id ); + for ( const auto &w : gpo.active_sons.at(type) ) { + _sso.current_shuffled_sons.push_back(w.son_id); + } auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32; - for( uint32_t i = 0; i < _sso.current_shuffled_sons.size(); ++i ) + + for (uint32_t i = 0; i < _sso.current_shuffled_sons.size(); ++i) { /// High performance random generator /// http://xorshift.di.unimi.it/ - uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL; + uint64_t k = now_hi + uint64_t(i) * 2685821657736338717ULL; k ^= (k >> 12); k ^= (k << 25); k ^= (k >> 27); k *= 2685821657736338717ULL; uint32_t jmax = _sso.current_shuffled_sons.size() - i; - uint32_t j = i + k%jmax; - std::swap( _sso.current_shuffled_sons[i], - _sso.current_shuffled_sons[j] ); + uint32_t j = i + k % jmax; + std::swap(_sso.current_shuffled_sons[i], + _sso.current_shuffled_sons[j]); } }); } @@ -304,13 +318,15 @@ void database::update_witness_schedule(const signed_block& next_block) idump( ( double(total_time/1000000.0)/calls) ); } -void database::update_son_schedule(const signed_block& next_block) +void database::update_son_schedule(sidechain_type type, const signed_block& next_block) { auto start = fc::time_point::now(); - const global_property_object& gpo = get_global_properties(); +#ifndef NDEBUG const son_schedule_object& sso = get(son_schedule_id_type()); - uint32_t schedule_needs_filled = gpo.active_sons.size(); - uint32_t schedule_slot = get_slot_at_time(next_block.timestamp); +#endif + const global_property_object& gpo = get_global_properties(); + const uint32_t schedule_needs_filled = gpo.active_sons.at(type).size(); + const uint32_t schedule_slot = get_slot_at_time(next_block.timestamp); // We shouldn't be able to generate _pending_block with timestamp // in the past, and incoming blocks from the network with timestamp @@ -319,48 +335,49 @@ void database::update_son_schedule(const signed_block& next_block) assert( schedule_slot > 0 ); - son_id_type first_son; - bool slot_is_near = sso.scheduler.get_slot( schedule_slot-1, first_son ); - - son_id_type son; - const dynamic_global_property_object& dpo = get_dynamic_global_properties(); assert( dpo.random.data_size() == witness_scheduler_rng::seed_length ); assert( witness_scheduler_rng::seed_length == sso.rng_seed.size() ); - modify(sso, [&](son_schedule_object& _sso) + const son_schedule_object& sidechain_sso = get(son_schedule_id_type(get_son_schedule_id(type))); + son_id_type first_son; + bool slot_is_near = sidechain_sso.scheduler.get_slot( schedule_slot-1, first_son ); + son_id_type son_id; + + modify(sidechain_sso, [&](son_schedule_object& _sso) { - _sso.slots_since_genesis += schedule_slot; - witness_scheduler_rng rng(sso.rng_seed.data, _sso.slots_since_genesis); + _sso.slots_since_genesis += schedule_slot; + witness_scheduler_rng rng(_sso.rng_seed.data, _sso.slots_since_genesis); - _sso.scheduler._min_token_count = std::max(int(gpo.active_sons.size()) / 2, 1); + _sso.scheduler._min_token_count = std::max(int(gpo.active_sons.at(type).size()) / 2, 1); - if( slot_is_near ) - { - uint32_t drain = schedule_slot; - while( drain > 0 ) - { - if( _sso.scheduler.size() == 0 ) - break; - _sso.scheduler.consume_schedule(); - --drain; - } - } - else - { - _sso.scheduler.reset_schedule( first_son ); - } - while( !_sso.scheduler.get_slot(schedule_needs_filled, son) ) - { - if( _sso.scheduler.produce_schedule(rng) & emit_turn ) - memcpy(_sso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); - } - _sso.last_scheduling_block = next_block.block_num(); - _sso.recent_slots_filled = ( - (_sso.recent_slots_filled << 1) - + 1) << (schedule_slot - 1); + if( slot_is_near ) + { + uint32_t drain = schedule_slot; + while( drain > 0 ) + { + if( _sso.scheduler.size() == 0 ) + break; + _sso.scheduler.consume_schedule(); + --drain; + } + } + else + { + _sso.scheduler.reset_schedule( first_son ); + } + while( !_sso.scheduler.get_slot(schedule_needs_filled, son_id) ) + { + if( _sso.scheduler.produce_schedule(rng) & emit_turn ) + memcpy(_sso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); + } + _sso.last_scheduling_block = next_block.block_num(); + _sso.recent_slots_filled = ( + (_sso.recent_slots_filled << 1) + + 1) << (schedule_slot - 1); }); + auto end = fc::time_point::now(); static uint64_t total_time = 0; static uint64_t calls = 0; diff --git a/libraries/chain/hardfork.d/SON3.hf b/libraries/chain/hardfork.d/SON3.hf deleted file mode 100644 index d9556e30..00000000 --- a/libraries/chain/hardfork.d/SON3.hf +++ /dev/null @@ -1,7 +0,0 @@ -#ifndef HARDFORK_SON3_TIME -#ifdef BUILD_PEERPLAYS_TESTNET -#define HARDFORK_SON3_TIME (fc::time_point_sec::from_iso_string("2022-07-16T00:00:00")) -#else -#define HARDFORK_SON3_TIME (fc::time_point_sec::from_iso_string("2022-07-16T00:00:00")) -#endif -#endif diff --git a/libraries/chain/hardfork.d/SON_FOR_ETHEREUM.hf b/libraries/chain/hardfork.d/SON_FOR_ETHEREUM.hf new file mode 100644 index 00000000..05ae0708 --- /dev/null +++ b/libraries/chain/hardfork.d/SON_FOR_ETHEREUM.hf @@ -0,0 +1,7 @@ +#ifndef HARDFORK_SON_FOR_ETHEREUM_TIME +#ifdef BUILD_PEERPLAYS_TESTNET +#define HARDFORK_SON_FOR_ETHEREUM_TIME (fc::time_point_sec::from_iso_string("2023-07-17T12:00:00")) +#else +#define HARDFORK_SON_FOR_ETHEREUM_TIME (fc::time_point_sec::from_iso_string("2023-10-24T12:00:00")) +#endif +#endif diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index ef9a0c80..9b55b7b5 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -158,7 +158,7 @@ #define GRAPHENE_RECENTLY_MISSED_COUNT_INCREMENT 4 #define GRAPHENE_RECENTLY_MISSED_COUNT_DECREMENT 3 -#define GRAPHENE_CURRENT_DB_VERSION "PPY2.4" +#define GRAPHENE_CURRENT_DB_VERSION "PPY2.5" #define GRAPHENE_IRREVERSIBLE_THRESHOLD (70 * GRAPHENE_1_PERCENT) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 50975174..24fb32d4 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -66,6 +66,8 @@ namespace graphene { namespace chain { database(); ~database(); + std::vector _hardfork_times; + enum validation_steps { skip_nothing = 0, @@ -243,7 +245,16 @@ namespace graphene { namespace chain { witness_id_type get_scheduled_witness(uint32_t slot_num)const; /** - * @brief Get the son scheduled for block production in a slot. + * @brief Get son schedule id for the given sidechain_type. + * + * type sidechain_type we getting schedule. + * + * returns Id of the schedule object. + */ + unsigned_int get_son_schedule_id(sidechain_type type)const; + + /** + * @brief Get the bitcoin or hive son scheduled for block production in a slot. * * slot_num always corresponds to a time in the future. * @@ -256,7 +267,7 @@ namespace graphene { namespace chain { * * Passing slot_num == 0 returns GRAPHENE_NULL_WITNESS */ - son_id_type get_scheduled_son(uint32_t slot_num)const; + son_id_type get_scheduled_son(sidechain_type type, uint32_t slot_num)const; /** * Get the time at which the given slot occurs. @@ -281,8 +292,8 @@ namespace graphene { namespace chain { vector get_near_witness_schedule()const; void update_witness_schedule(); void update_witness_schedule(const signed_block& next_block); - void update_son_schedule(); - void update_son_schedule(const signed_block& next_block); + void update_son_schedule(sidechain_type type); + void update_son_schedule(sidechain_type type, const signed_block& next_block); void check_lottery_end_by_participants( asset_id_type asset_id ); void check_ending_lotteries(); @@ -311,7 +322,7 @@ namespace graphene { namespace chain { fc::optional create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ); signed_transaction create_signed_transaction( const fc::ecc::private_key& signing_private_key, const operation& op ); bool is_son_dereg_valid( son_id_type son_id ); - bool is_son_active( son_id_type son_id ); + bool is_son_active( sidechain_type type, son_id_type son_id ); bool is_asset_creation_allowed(const string& symbol); time_point_sec head_block_time()const; @@ -332,6 +343,8 @@ namespace graphene { namespace chain { void initialize_evaluators(); /// Reset the object graph in-memory void initialize_indexes(); + void initialize_hardforks(); + void init_genesis(const genesis_state_type& genesis_state = genesis_state_type()); template @@ -507,12 +520,16 @@ namespace graphene { namespace chain { void notify_changed_objects(); private: + std::mutex _pending_tx_session_mutex; optional _pending_tx_session; vector< unique_ptr > _operation_evaluators; template vector> sort_votable_objects(size_t count)const; + template + vector> sort_votable_objects(sidechain_type sidechain, size_t count)const; + //////////////////// db_block.cpp //////////////////// public: @@ -562,18 +579,20 @@ namespace graphene { namespace chain { void initialize_budget_record( fc::time_point_sec now, budget_record& rec )const; void process_budget(); void pay_workers( share_type& budget ); - void pay_sons(); + void pay_sons_before_hf_ethereum(); + void pay_sons_after_hf_ethereum(); void perform_son_tasks(); void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); void update_active_witnesses(); void update_active_committee_members(); - void update_son_metrics( const vector& curr_active_sons ); + void update_son_metrics( const flat_map >& curr_active_sons ); void update_active_sons(); void remove_son_proposal( const proposal_object& proposal ); void remove_inactive_son_down_proposals( const vector& son_ids_to_remove ); void remove_inactive_son_proposals( const vector& son_ids_to_remove ); - void update_son_statuses( const vector& cur_active_sons, const vector& new_active_sons ); - void update_son_wallet( const vector& new_active_sons ); + void update_son_statuses( const flat_map >& curr_active_sons, + const flat_map >& new_active_sons ); + void update_son_wallet( const flat_map >& new_active_sons ); void update_worker_votes(); public: @@ -585,6 +604,7 @@ namespace graphene { namespace chain { ///@} ///@} + std::mutex _pending_tx_mutex; vector< processed_transaction > _pending_tx; fork_database _fork_db; @@ -612,11 +632,17 @@ namespace graphene { namespace chain { uint16_t _current_op_in_trx = 0; uint32_t _current_virtual_op = 0; - vector _vote_tally_buffer; - vector _witness_count_histogram_buffer; - vector _committee_count_histogram_buffer; - vector _son_count_histogram_buffer; - uint64_t _total_voting_stake; + vector _vote_tally_buffer; + vector _witness_count_histogram_buffer; + vector _committee_count_histogram_buffer; + flat_map > _son_count_histogram_buffer = []{ + flat_map > son_count_histogram_buffer; + for(const auto& active_sidechain_type : all_sidechain_types){ + son_count_histogram_buffer[active_sidechain_type] = vector{}; + } + return son_count_histogram_buffer; + }(); + uint64_t _total_voting_stake; flat_map _checkpoints; diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 53bdec08..78798202 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -27,7 +27,7 @@ #include #include #include -#include +#include #include namespace graphene { namespace chain { @@ -49,10 +49,18 @@ namespace graphene { namespace chain { chain_parameters parameters; optional pending_parameters; - uint32_t next_available_vote_id = 0; - vector active_committee_members; // updated once per maintenance interval - flat_set active_witnesses; // updated once per maintenance interval - vector active_sons; // updated once per maintenance interval + uint32_t next_available_vote_id = 0; + vector active_committee_members; // updated once per maintenance interval + flat_set active_witnesses; // updated once per maintenance interval + flat_map > active_sons = []() // updated once per maintenance interval + { + flat_map > active_sons; + for(const auto& active_sidechain_type : all_sidechain_types) + { + active_sons[active_sidechain_type] = vector(); + } + return active_sons; + }(); // n.b. witness scheduling is done by witness_schedule object }; diff --git a/libraries/chain/include/graphene/chain/nft_object.hpp b/libraries/chain/include/graphene/chain/nft_object.hpp index fe026da5..a54992c4 100644 --- a/libraries/chain/include/graphene/chain/nft_object.hpp +++ b/libraries/chain/include/graphene/chain/nft_object.hpp @@ -130,6 +130,9 @@ namespace graphene { namespace chain { std::greater< uint32_t >, std::greater< object_id_type > > + >, + ordered_non_unique< tag, + member > > >; diff --git a/libraries/chain/include/graphene/chain/protocol/account.hpp b/libraries/chain/include/graphene/chain/protocol/account.hpp index 50ccb8ae..c46caac5 100644 --- a/libraries/chain/include/graphene/chain/protocol/account.hpp +++ b/libraries/chain/include/graphene/chain/protocol/account.hpp @@ -28,6 +28,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -35,8 +36,28 @@ namespace graphene { namespace chain { bool is_cheap_name( const string& n ); /// These are the fields which can be updated by the active authority. - struct account_options + struct account_options { + struct ext + { + /// The number of active son members this account votes the blockchain should appoint + /// Must not exceed the actual number of son members voted for in @ref votes + optional< flat_map > num_son; + + /// Returns and empty num_son map with all sidechains + static flat_map empty_num_son() + { + flat_map num_son; + for(const auto& active_sidechain_type : all_sidechain_types) + { + num_son[active_sidechain_type] = 0; + } + + return num_son; + } + }; + + /// The memo key is the key this account will typically use to encrypt/sign transaction memos and other non- /// validated account activities. This field is here to prevent confusion if the active authority has zero or /// multiple keys in it. @@ -52,14 +73,11 @@ namespace graphene { namespace chain { /// The number of active committee members this account votes the blockchain should appoint /// Must not exceed the actual number of committee members voted for in @ref votes uint16_t num_committee = 0; - /// The number of active son members this account votes the blockchain should appoint - /// Must not exceed the actual number of son members voted for in @ref votes - uint16_t num_son = 0; /// This is the list of vote IDs this account votes for. The weight of these votes is determined by this /// account's balance of core asset. flat_set votes; - extensions_type extensions; - + extension< ext > extensions; + /// Whether this account is voting inline bool is_voting() const { @@ -244,7 +262,7 @@ namespace graphene { namespace chain { */ struct account_upgrade_operation : public base_operation { - struct fee_parameters_type { + struct fee_parameters_type { uint64_t membership_annual_fee = 2000 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t membership_lifetime_fee = 10000 * GRAPHENE_BLOCKCHAIN_PRECISION; ///< the cost to upgrade to a lifetime member }; @@ -289,6 +307,7 @@ namespace graphene { namespace chain { } } // graphene::chain +FC_REFLECT(graphene::chain::account_options::ext, (num_son)) FC_REFLECT(graphene::chain::account_options, (memo_key)(voting_account)(num_witness)(num_committee)(votes)(extensions)) // FC_REFLECT_TYPENAME( graphene::chain::account_whitelist_operation::account_listing) FC_REFLECT_ENUM( graphene::chain::account_whitelist_operation::account_listing, diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index 3a11e99f..94493f30 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -70,6 +70,7 @@ namespace graphene { namespace chain { optional < uint16_t > maximum_son_count = GRAPHENE_DEFAULT_MAX_SONS; ///< maximum number of active SONS optional < asset_id_type > hbd_asset = asset_id_type(); optional < asset_id_type > hive_asset = asset_id_type(); + optional < asset_id_type > eth_asset = asset_id_type(); }; struct chain_parameters @@ -220,6 +221,9 @@ namespace graphene { namespace chain { inline asset_id_type hive_asset() const { return extensions.value.hive_asset.valid() ? *extensions.value.hive_asset : asset_id_type(); } + inline asset_id_type eth_asset() const { + return extensions.value.eth_asset.valid() ? *extensions.value.eth_asset : asset_id_type(); + } private: static void safe_copy(chain_parameters& to, const chain_parameters& from); }; @@ -257,6 +261,7 @@ FC_REFLECT( graphene::chain::parameter_extension, (maximum_son_count) (hbd_asset) (hive_asset) + (eth_asset) ) FC_REFLECT( graphene::chain::chain_parameters, diff --git a/libraries/chain/include/graphene/chain/protocol/confidential.hpp b/libraries/chain/include/graphene/chain/protocol/confidential.hpp index 697ef35b..86628f3b 100644 --- a/libraries/chain/include/graphene/chain/protocol/confidential.hpp +++ b/libraries/chain/include/graphene/chain/protocol/confidential.hpp @@ -111,12 +111,12 @@ struct stealth_confirmation /** * Packs *this then encodes as base58 encoded string. */ - operator string()const; + //operator string()const; /** * Unpacks from a base58 string */ - stealth_confirmation( const std::string& base58 ); - stealth_confirmation(){} + //stealth_confirmation( const std::string& base58 ); + //stealth_confirmation(){} public_key_type one_time_key; optional to; @@ -152,7 +152,6 @@ struct transfer_to_blind_operation : public base_operation uint32_t price_per_output = 5*GRAPHENE_BLOCKCHAIN_PRECISION; }; - asset fee; asset amount; account_id_type from; @@ -160,8 +159,8 @@ struct transfer_to_blind_operation : public base_operation vector outputs; account_id_type fee_payer()const { return from; } - void validate()const; - share_type calculate_fee(const fee_parameters_type& )const; + //void validate()const; + //share_type calculate_fee(const fee_parameters_type& )const; }; /** @@ -181,13 +180,12 @@ struct transfer_from_blind_operation : public base_operation vector inputs; account_id_type fee_payer()const { return GRAPHENE_TEMP_ACCOUNT; } - void validate()const; - - void get_required_authorities( vector& a )const - { - for( const auto& in : inputs ) - a.push_back( in.owner ); - } + //void validate()const; + //void get_required_authorities( vector& a )const + //{ + // for( const auto& in : inputs ) + // a.push_back( in.owner ); + //} }; /** @@ -243,17 +241,16 @@ struct blind_transfer_operation : public base_operation asset fee; vector inputs; vector outputs; - - /** graphene TEMP account */ - account_id_type fee_payer()const; - void validate()const; - share_type calculate_fee( const fee_parameters_type& k )const; - void get_required_authorities( vector& a )const - { - for( const auto& in : inputs ) - a.push_back( in.owner ); - } + /** graphene TEMP account */ + account_id_type fee_payer()const { return GRAPHENE_TEMP_ACCOUNT; } + //void validate()const; + //share_type calculate_fee( const fee_parameters_type& k )const; + //void get_required_authorities( vector& a )const + //{ + // for( const auto& in : inputs ) + // a.push_back( in.owner ); + //} }; ///@} endgroup stealth diff --git a/libraries/chain/include/graphene/chain/protocol/nft_lottery.hpp b/libraries/chain/include/graphene/chain/protocol/nft_lottery.hpp index 0c8ea855..00ea31b0 100644 --- a/libraries/chain/include/graphene/chain/protocol/nft_lottery.hpp +++ b/libraries/chain/include/graphene/chain/protocol/nft_lottery.hpp @@ -18,7 +18,7 @@ namespace graphene // Buyer purchasing lottery tickets account_id_type buyer; // count of tickets to buy - uint64_t tickets_to_buy; + share_type tickets_to_buy; // amount that can spent asset amount; @@ -83,4 +83,4 @@ FC_REFLECT(graphene::chain::nft_lottery_reward_operation::fee_parameters_type, ( FC_REFLECT(graphene::chain::nft_lottery_end_operation::fee_parameters_type, (fee)) FC_REFLECT(graphene::chain::nft_lottery_token_purchase_operation, (fee)(lottery_id)(buyer)(tickets_to_buy)(amount)(extensions)) FC_REFLECT(graphene::chain::nft_lottery_reward_operation, (fee)(lottery_id)(winner)(amount)(win_percentage)(is_benefactor_reward)(winner_ticket_id)(extensions)) -FC_REFLECT(graphene::chain::nft_lottery_end_operation, (fee)(lottery_id)(extensions)) \ No newline at end of file +FC_REFLECT(graphene::chain::nft_lottery_end_operation, (fee)(lottery_id)(extensions)) diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 83d347ab..8cbde2ac 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -106,9 +106,9 @@ namespace graphene { namespace chain { assert_operation, balance_claim_operation, override_transfer_operation, - transfer_to_blind_operation, - blind_transfer_operation, - transfer_from_blind_operation, + transfer_to_blind_operation, //! We don't use this operation + blind_transfer_operation, //! We don't use this operation + transfer_from_blind_operation, //! We don't use this operation asset_settle_cancel_operation, // VIRTUAL asset_claim_fees_operation, fba_distribute_operation, // VIRTUAL diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp index 01a4ce74..593432bf 100644 --- a/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp @@ -71,7 +71,7 @@ FC_REFLECT( graphene::chain::sidechain_transaction_create_operation, (fee)(payer (sidechain) (object_id) (transaction) - (signers) ) + (signers)) FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation::fee_parameters_type, (fee) ) FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation, (fee)(signer)(payer) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp index 5194bed2..3165554f 100644 --- a/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp @@ -1,40 +1,47 @@ #pragma once #include #include +#include namespace graphene { namespace chain { - struct son_wallet_recreate_operation : public base_operation - { - struct fee_parameters_type { uint64_t fee = 0; }; + struct son_wallet_recreate_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + struct ext + { + optional > > sidechain_sons; + }; - asset fee; - account_id_type payer; + asset fee; + account_id_type payer; - vector sons; + vector sons; + extension< ext > extensions; - account_id_type fee_payer()const { return payer; } - share_type calculate_fee(const fee_parameters_type& k)const { return 0; } - }; + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; - struct son_wallet_update_operation : public base_operation - { - struct fee_parameters_type { uint64_t fee = 0; }; + struct son_wallet_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; - asset fee; - account_id_type payer; + asset fee; + account_id_type payer; - son_wallet_id_type son_wallet_id; - sidechain_type sidechain; - string address; + son_wallet_id_type son_wallet_id; + sidechain_type sidechain; + string address; - account_id_type fee_payer()const { return payer; } - share_type calculate_fee(const fee_parameters_type& k)const { return 0; } - }; + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; } } // namespace graphene::chain FC_REFLECT(graphene::chain::son_wallet_recreate_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::son_wallet_recreate_operation, (fee)(payer)(sons) ) +FC_REFLECT(graphene::chain::son_wallet_recreate_operation::ext, (sidechain_sons)) +FC_REFLECT(graphene::chain::son_wallet_recreate_operation, (fee)(payer)(sons)(extensions) ) FC_REFLECT(graphene::chain::son_wallet_update_operation::fee_parameters_type, (fee) ) FC_REFLECT(graphene::chain::son_wallet_update_operation, (fee)(payer)(son_wallet_id)(sidechain)(address) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 098e5dd1..33c7df66 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -395,6 +395,13 @@ namespace graphene { namespace chain { bool is_valid_muse( const std::string& base58str ); }; + class pubkey_comparator { + public: + inline bool operator()(const public_key_type& a, const public_key_type& b) const { + return a.key_data < b.key_data; + } + }; + struct extended_public_key_type { struct binary_key diff --git a/libraries/chain/include/graphene/chain/protocol/vote.hpp b/libraries/chain/include/graphene/chain/protocol/vote.hpp index 8a46954d..913f6c5b 100644 --- a/libraries/chain/include/graphene/chain/protocol/vote.hpp +++ b/libraries/chain/include/graphene/chain/protocol/vote.hpp @@ -59,7 +59,9 @@ struct vote_id_type committee, witness, worker, - son, + son_bitcoin, + son_hive, + son_ethereum, VOTE_TYPE_COUNT }; @@ -144,7 +146,7 @@ void from_variant( const fc::variant& var, graphene::chain::vote_id_type& vo, ui FC_REFLECT_TYPENAME( fc::flat_set ) -FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(son)(VOTE_TYPE_COUNT) ) +FC_REFLECT_ENUM( graphene::chain::vote_id_type::vote_type, (witness)(committee)(worker)(son_bitcoin)(son_hive)(son_ethereum)(VOTE_TYPE_COUNT) ) FC_REFLECT( graphene::chain::vote_id_type, (content) ) GRAPHENE_EXTERNAL_SERIALIZATION( extern, graphene::chain::vote_id_type ) diff --git a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp index b8aa07c6..b808a23c 100644 --- a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp +++ b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp @@ -31,11 +31,20 @@ namespace graphene { namespace chain { time_point_sec expires; sidechain_address_object() : - sidechain(sidechain_type::bitcoin), + sidechain(sidechain_type::bitcoin), //! FIXME - bitcoin ??? deposit_public_key(""), deposit_address(""), withdraw_public_key(""), withdraw_address("") {} + + inline string get_deposit_address() const { + if(sidechain_type::ethereum != sidechain) + return deposit_address; + + auto deposit_address_lower = deposit_address; + std::transform(deposit_address_lower.begin(), deposit_address_lower.end(), deposit_address_lower.begin(), ::tolower); + return deposit_address_lower; + } }; struct by_account; @@ -76,7 +85,7 @@ namespace graphene { namespace chain { ordered_non_unique< tag, composite_key, - member, + const_mem_fun, member > > diff --git a/libraries/chain/include/graphene/chain/sidechain_defs.hpp b/libraries/chain/include/graphene/chain/sidechain_defs.hpp index 7f986f96..421ef597 100644 --- a/libraries/chain/include/graphene/chain/sidechain_defs.hpp +++ b/libraries/chain/include/graphene/chain/sidechain_defs.hpp @@ -1,6 +1,11 @@ #pragma once +#include + +#include + #include +#include namespace graphene { namespace chain { @@ -13,12 +18,28 @@ enum class sidechain_type { hive }; -} } +static const std::set all_sidechain_types = {sidechain_type::bitcoin, sidechain_type::ethereum, sidechain_type::hive}; + +inline std::set active_sidechain_types(const fc::time_point_sec block_time) { + std::set active_sidechain_types{}; + + if (block_time >= HARDFORK_SON_TIME) + active_sidechain_types.insert(sidechain_type::bitcoin); + if (block_time >= HARDFORK_SON_FOR_HIVE_TIME) + active_sidechain_types.insert(sidechain_type::hive); + if (block_time >= HARDFORK_SON_FOR_ETHEREUM_TIME) + active_sidechain_types.insert(sidechain_type::ethereum); + + return active_sidechain_types; +} + +} // namespace chain +} // namespace graphene FC_REFLECT_ENUM(graphene::chain::sidechain_type, - (unknown) - (bitcoin) - (ethereum) - (eos) - (hive) - (peerplays) ) + (unknown) + (bitcoin) + (ethereum) + (eos) + (hive) + (peerplays) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp index 30a0dd5e..171c564a 100644 --- a/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace graphene { namespace chain { using namespace graphene::db; @@ -30,7 +30,7 @@ namespace graphene { namespace chain { sidechain_type sidechain = sidechain_type::unknown; object_id_type object_id; std::string transaction; - std::vector signers; + std::vector signers; std::vector> signatures; std::string sidechain_transaction; diff --git a/libraries/chain/include/graphene/chain/son_info.hpp b/libraries/chain/include/graphene/chain/son_info.hpp index 2bfecac4..afaf9506 100644 --- a/libraries/chain/include/graphene/chain/son_info.hpp +++ b/libraries/chain/include/graphene/chain/son_info.hpp @@ -3,12 +3,11 @@ #include namespace graphene { namespace chain { - using namespace graphene::db; /** - * @class son_info - * @brief tracks information about a SON info required to re/create primary wallet - * @ingroup object + * @class son_info + * @brief tracks information about a SON info required to re/create primary wallet + * @ingroup object */ struct son_info { son_id_type son_id; @@ -26,11 +25,11 @@ namespace graphene { namespace chain { if (son_sets_equal) { bool sidechain_public_keys_equal = true; for (size_t i = 0; i < sidechain_public_keys.size(); i++) { - const auto lhs_scpk = sidechain_public_keys.nth(i); - const auto rhs_scpk = rhs.sidechain_public_keys.nth(i); - sidechain_public_keys_equal = sidechain_public_keys_equal && - (lhs_scpk->first == rhs_scpk->first) && - (lhs_scpk->second == rhs_scpk->second); + const auto lhs_scpk = sidechain_public_keys.nth(i); + const auto rhs_scpk = rhs.sidechain_public_keys.nth(i); + sidechain_public_keys_equal = sidechain_public_keys_equal && + (lhs_scpk->first == rhs_scpk->first) && + (lhs_scpk->second == rhs_scpk->second); } son_sets_equal = son_sets_equal && sidechain_public_keys_equal; } @@ -40,8 +39,4 @@ namespace graphene { namespace chain { } } -FC_REFLECT( graphene::chain::son_info, - (son_id) - (weight) - (signing_key) - (sidechain_public_keys) ) +FC_REFLECT( graphene::chain::son_info, (son_id) (weight) (signing_key) (sidechain_public_keys) ) diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp index d0b74e79..61ada2e5 100644 --- a/libraries/chain/include/graphene/chain/son_object.hpp +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -35,15 +35,15 @@ namespace graphene { namespace chain { // Transactions signed since the last son payouts flat_map txs_signed; // Total Voted Active time i.e. duration selected as part of voted active SONs - uint64_t total_voted_time = 0; + flat_map total_voted_time; // Total Downtime barring the current down time in seconds, used for stats to present to user - uint64_t total_downtime = 0; + flat_map total_downtime; // Current Interval Downtime since last maintenance - uint64_t current_interval_downtime = 0; + flat_map current_interval_downtime; // Down timestamp, if son status is in_maintenance use this - fc::time_point_sec last_down_timestamp; + flat_map last_down_timestamp; // Last Active heartbeat timestamp - fc::time_point_sec last_active_timestamp; + flat_map last_active_timestamp; // Deregistered Timestamp fc::time_point_sec deregistered_timestamp; // Total sidechain transactions reported by SON network while SON was active @@ -64,23 +64,48 @@ namespace graphene { namespace chain { static const uint8_t type_id = son_object_type; account_id_type son_account; - vote_id_type vote_id; - uint64_t total_votes = 0; + flat_map sidechain_vote_ids; + flat_map total_votes = []() + { + flat_map total_votes; + for(const auto& active_sidechain_type : all_sidechain_types) + { + total_votes[active_sidechain_type] = 0; + } + return total_votes; + }(); string url; vesting_balance_id_type deposit; public_key_type signing_key; vesting_balance_id_type pay_vb; son_statistics_id_type statistics; - son_status status = son_status::inactive; + flat_map statuses = []() + { + flat_map statuses; + for(const auto& active_sidechain_type : all_sidechain_types) + { + statuses[active_sidechain_type] = son_status::inactive; + } + return statuses; + }(); flat_map sidechain_public_keys; void pay_son_fee(share_type pay, database& db); - bool has_valid_config()const; - bool has_valid_config(time_point_sec head_block_time)const; + bool has_valid_config(time_point_sec head_block_time, sidechain_type sidechain) const; + + inline optional get_sidechain_vote_id(sidechain_type sidechain) const { return sidechain_vote_ids.contains(sidechain) ? sidechain_vote_ids.at(sidechain) : optional{}; } + inline optional get_bitcoin_vote_id() const { return get_sidechain_vote_id(sidechain_type::bitcoin); } + inline optional get_hive_vote_id() const { return get_sidechain_vote_id(sidechain_type::hive); } + inline optional get_ethereum_vote_id() const { return get_sidechain_vote_id(sidechain_type::ethereum); } + + private: + bool has_valid_config(sidechain_type sidechain) const; }; struct by_account; - struct by_vote_id; + struct by_vote_id_bitcoin; + struct by_vote_id_hive; + struct by_vote_id_ethereum; using son_multi_index_type = multi_index_container< son_object, indexed_by< @@ -90,8 +115,14 @@ namespace graphene { namespace chain { ordered_unique< tag, member >, - ordered_unique< tag, - member + ordered_non_unique< tag, + const_mem_fun, &son_object::get_bitcoin_vote_id> + >, + ordered_non_unique< tag, + const_mem_fun, &son_object::get_hive_vote_id> + >, + ordered_non_unique< tag, + const_mem_fun, &son_object::get_ethereum_vote_id> > > >; @@ -117,14 +148,14 @@ FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(request_maintena FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object), (son_account) - (vote_id) + (sidechain_vote_ids) (total_votes) (url) (deposit) (signing_key) (pay_vb) (statistics) - (status) + (statuses) (sidechain_public_keys) ) diff --git a/libraries/chain/include/graphene/chain/son_sidechain_info.hpp b/libraries/chain/include/graphene/chain/son_sidechain_info.hpp new file mode 100644 index 00000000..8d81f2a4 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_sidechain_info.hpp @@ -0,0 +1,31 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + /** + * @class son_sidechain_info + * @brief tracks information about a SON info required to re/create primary wallet + * @ingroup object + */ + struct son_sidechain_info { + son_id_type son_id; + weight_type weight = 0; + public_key_type signing_key; + string public_key; + + bool operator==(const son_sidechain_info& rhs) const { + bool son_sets_equal = + (son_id == rhs.son_id) && + (weight == rhs.weight) && + (signing_key == rhs.signing_key) && + (public_key == rhs.public_key); + + return son_sets_equal; + } + }; + +} } + +FC_REFLECT( graphene::chain::son_sidechain_info, (son_id) (weight) (signing_key) (public_key) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_object.hpp index 315def33..79ffb2e0 100644 --- a/libraries/chain/include/graphene/chain/son_wallet_object.hpp +++ b/libraries/chain/include/graphene/chain/son_wallet_object.hpp @@ -1,6 +1,6 @@ #pragma once #include -#include +#include #include namespace graphene { namespace chain { @@ -21,7 +21,7 @@ namespace graphene { namespace chain { time_point_sec expires; flat_map addresses; - vector sons; + flat_map > sons; }; struct by_valid_from; diff --git a/libraries/chain/include/graphene/chain/voters_info.hpp b/libraries/chain/include/graphene/chain/voters_info.hpp index 53b0e74a..86f3e9cc 100644 --- a/libraries/chain/include/graphene/chain/voters_info.hpp +++ b/libraries/chain/include/graphene/chain/voters_info.hpp @@ -19,11 +19,11 @@ namespace graphene { namespace chain { * @ingroup object */ struct voters_info { - optional voters_for_committee_member; - optional voters_for_witness; - optional > voters_for_workers; - optional > voters_against_workers; - optional voters_for_son; + optional voters_for_committee_member; + optional voters_for_witness; + optional > voters_for_workers; + optional > voters_against_workers; + optional > voters_for_son; }; } } // graphene::chain @@ -37,4 +37,4 @@ FC_REFLECT( graphene::chain::voters_info, (voters_for_witness) (voters_for_workers) (voters_against_workers) - (voters_for_son) ) \ No newline at end of file + (voters_for_son)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/votes_info.hpp b/libraries/chain/include/graphene/chain/votes_info.hpp index 0a515589..f405d83a 100644 --- a/libraries/chain/include/graphene/chain/votes_info.hpp +++ b/libraries/chain/include/graphene/chain/votes_info.hpp @@ -19,11 +19,11 @@ namespace graphene { namespace chain { * @ingroup object */ struct votes_info { - optional< vector< votes_info_object > > votes_for_committee_members; - optional< vector< votes_info_object > > votes_for_witnesses; - optional< vector< votes_info_object > > votes_for_workers; - optional< vector< votes_info_object > > votes_against_workers; - optional< vector< votes_info_object > > votes_for_sons; + optional< vector< votes_info_object > > votes_for_committee_members; + optional< vector< votes_info_object > > votes_for_witnesses; + optional< vector< votes_info_object > > votes_for_workers; + optional< vector< votes_info_object > > votes_against_workers; + optional< flat_map > > votes_for_sons; }; } } // graphene::chain @@ -37,4 +37,4 @@ FC_REFLECT( graphene::chain::votes_info, (votes_for_witnesses) (votes_for_workers) (votes_against_workers) - (votes_for_sons) ) \ No newline at end of file + (votes_for_sons)) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp index 2eff563f..945b218d 100644 --- a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp @@ -96,7 +96,7 @@ class son_schedule_object : public graphene::db::abstract_object current_shuffled_sons; + vector current_shuffled_sons; son_scheduler scheduler; uint32_t last_scheduling_block; diff --git a/libraries/chain/include/graphene/chain/witness_scheduler.hpp b/libraries/chain/include/graphene/chain/witness_scheduler.hpp index 42dfe149..40e0e1d2 100644 --- a/libraries/chain/include/graphene/chain/witness_scheduler.hpp +++ b/libraries/chain/include/graphene/chain/witness_scheduler.hpp @@ -162,8 +162,12 @@ class generic_witness_scheduler _schedule.pop_front(); auto it = _lame_duck.find( result ); - if( it != _lame_duck.end() ) - _lame_duck.erase( it ); + if( it != _lame_duck.end() ) { + set< WitnessID > removal_set; + removal_set.insert(*it); + remove_all( removal_set ); + _lame_duck.erase(it); + } if( debug ) check_invariant(); return result; } @@ -389,7 +393,7 @@ class generic_witness_scheduler // scheduled std::deque < WitnessID > _schedule; - // in _schedule, but not to be replaced + // in _schedule, but must be removed set< WitnessID > _lame_duck; }; diff --git a/libraries/chain/nft_lottery_evaluator.cpp b/libraries/chain/nft_lottery_evaluator.cpp index 794616cf..2d9fe7f0 100644 --- a/libraries/chain/nft_lottery_evaluator.cpp +++ b/libraries/chain/nft_lottery_evaluator.cpp @@ -30,7 +30,7 @@ namespace graphene auto lottery_options = lottery_md_obj.lottery_data->lottery_options; FC_ASSERT(lottery_options.ticket_price.asset_id == op.amount.asset_id); - FC_ASSERT((double)op.amount.amount.value / lottery_options.ticket_price.amount.value == (double)op.tickets_to_buy); + FC_ASSERT(op.tickets_to_buy * lottery_options.ticket_price.amount.value == op.amount.amount.value); return void_result(); } FC_CAPTURE_AND_RETHROW((op)) @@ -142,4 +142,4 @@ namespace graphene FC_CAPTURE_AND_RETHROW((op)) } } // namespace chain -} // namespace graphene \ No newline at end of file +} // namespace graphene diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index 2405369a..2a5c1045 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -174,22 +174,37 @@ void account_options::validate() const { auto needed_witnesses = num_witness; auto needed_committee = num_committee; - auto needed_sons = num_son; for( vote_id_type id : votes ) if( id.type() == vote_id_type::witness && needed_witnesses ) --needed_witnesses; else if ( id.type() == vote_id_type::committee && needed_committee ) --needed_committee; - else if ( id.type() == vote_id_type::son && needed_sons ) - --needed_sons; FC_ASSERT( needed_witnesses == 0, "May not specify fewer witnesses than the number voted for."); FC_ASSERT( needed_committee == 0, "May not specify fewer committee members than the number voted for."); - FC_ASSERT( needed_sons == 0, - "May not specify fewer SONs than the number voted for."); + + if ( extensions.value.num_son.valid() ) + { + flat_map needed_sons = *extensions.value.num_son; + + for( vote_id_type id : votes ) + if ( id.type() == vote_id_type::son_bitcoin && needed_sons[sidechain_type::bitcoin] ) + --needed_sons[sidechain_type::bitcoin]; + else if ( id.type() == vote_id_type::son_hive && needed_sons[sidechain_type::hive] ) + --needed_sons[sidechain_type::hive]; + else if ( id.type() == vote_id_type::son_ethereum && needed_sons[sidechain_type::ethereum] ) + --needed_sons[sidechain_type::ethereum]; + + FC_ASSERT( needed_sons[sidechain_type::bitcoin] == 0, + "May not specify fewer Bitcoin SONs than the number voted for."); + FC_ASSERT( needed_sons[sidechain_type::hive] == 0, + "May not specify fewer Hive SONs than the number voted for."); + FC_ASSERT( needed_sons[sidechain_type::ethereum] == 0, + "May not specify fewer Ethereum SONs than the number voted for."); + } } void affiliate_reward_distribution::validate() const diff --git a/libraries/chain/protocol/confidential.cpp b/libraries/chain/protocol/confidential.cpp index 2e8fbc68..4bca850d 100644 --- a/libraries/chain/protocol/confidential.cpp +++ b/libraries/chain/protocol/confidential.cpp @@ -22,12 +22,10 @@ * THE SOFTWARE. */ #include -#include -#include -#include #include +/* namespace graphene { namespace chain { void transfer_to_blind_operation::validate()const @@ -47,19 +45,6 @@ void transfer_to_blind_operation::validate()const FC_ASSERT( !outputs[i].owner.is_impossible() ); } FC_ASSERT( out.size(), "there must be at least one output" ); - - auto public_c = fc::ecc::blind(blinding_factor,net_public); - - FC_ASSERT( fc::ecc::verify_sum( {public_c}, out, 0 ), "", ("net_public",net_public) ); - - if( outputs.size() > 1 ) - { - for( auto out : outputs ) - { - auto info = fc::ecc::range_get_info( out.range_proof ); - FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY ); - } - } } share_type transfer_to_blind_operation::calculate_fee( const fee_parameters_type& k )const @@ -79,31 +64,15 @@ void transfer_from_blind_operation::validate()const vector in(inputs.size()); vector out; int64_t net_public = fee.amount.value + amount.amount.value; - out.push_back( fc::ecc::blind( blinding_factor, net_public ) ); - for( uint32_t i = 0; i < in.size(); ++i ) - { - in[i] = inputs[i].commitment; - /// by requiring all inputs to be sorted we also prevent duplicate commitments on the input - if( i > 0 ) FC_ASSERT( in[i-1] < in[i], "all inputs must be sorted by commitment id" ); - } - FC_ASSERT( in.size(), "there must be at least one input" ); - FC_ASSERT( fc::ecc::verify_sum( in, out, 0 ) ); } -/** - * If fee_payer = temp_account_id, then the fee is paid by the surplus balance of inputs-outputs and - * 100% of the fee goes to the network. - */ account_id_type blind_transfer_operation::fee_payer()const { return GRAPHENE_TEMP_ACCOUNT; } -/** - * This method can be computationally intensive because it verifies that input commitments - output commitments add up to 0 - */ void blind_transfer_operation::validate()const { try { vector in(inputs.size()); @@ -122,17 +91,6 @@ void blind_transfer_operation::validate()const FC_ASSERT( !outputs[i].owner.is_impossible() ); } FC_ASSERT( in.size(), "there must be at least one input" ); - FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), "", ("net_public", net_public) ); - - if( outputs.size() > 1 ) - { - for( auto out : outputs ) - { - auto info = fc::ecc::range_get_info( out.range_proof ); - FC_ASSERT( info.max_value <= GRAPHENE_MAX_SHARE_SUPPLY ); - } - } - FC_ASSERT( fc::ecc::verify_sum( in, out, net_public ), "", ("net_public", net_public) ); } FC_CAPTURE_AND_RETHROW( (*this) ) } share_type blind_transfer_operation::calculate_fee( const fee_parameters_type& k )const @@ -140,16 +98,12 @@ share_type blind_transfer_operation::calculate_fee( const fee_parameters_type& k return k.fee + outputs.size() * k.price_per_output; } -/** - * Packs *this then encodes as base58 encoded string. - */ + stealth_confirmation::operator string()const { return fc::to_base58( fc::raw::pack( *this ) ); } -/** - * Unpacks from a base58 string - */ + stealth_confirmation::stealth_confirmation( const std::string& base58 ) { *this = fc::raw::unpack( fc::from_base58( base58 ) ); @@ -157,6 +111,8 @@ stealth_confirmation::stealth_confirmation( const std::string& base58 ) } } // graphene::chain +*/ + GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::transfer_to_blind_operation::fee_parameters_type ) GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::transfer_from_blind_operation::fee_parameters_type ) GRAPHENE_EXTERNAL_SERIALIZATION( /*not extern*/, graphene::chain::blind_transfer_operation::fee_parameters_type ) diff --git a/libraries/chain/sidechain_address_evaluator.cpp b/libraries/chain/sidechain_address_evaluator.cpp index 44408d42..3cffc61e 100644 --- a/libraries/chain/sidechain_address_evaluator.cpp +++ b/libraries/chain/sidechain_address_evaluator.cpp @@ -50,7 +50,7 @@ void_result update_sidechain_address_evaluator::do_evaluate(const sidechain_addr { try { const auto& sidx = db().get_index_type().indices().get(); const auto& son_obj = sidx.find(op.payer); - FC_ASSERT( son_obj != sidx.end() && db().is_son_active(son_obj->id), "Non active SON trying to update deposit address object" ); + FC_ASSERT( son_obj != sidx.end() && db().is_son_active(op.sidechain, son_obj->id), "Non active SON trying to update deposit address object" ); const auto& sdpke_idx = db().get_index_type().indices().get(); FC_ASSERT( op.deposit_address.valid() && op.deposit_public_key.valid() && op.deposit_address_data.valid(), "Update operation by SON is not valid"); FC_ASSERT( (*op.deposit_address).length() > 0 && (*op.deposit_public_key).length() > 0 && (*op.deposit_address_data).length() > 0, "SON should create a valid deposit address with valid deposit public key"); @@ -108,7 +108,6 @@ void_result delete_sidechain_address_evaluator::do_apply(const sidechain_address { try { const auto& idx = db().get_index_type().indices().get(); auto sidechain_address = idx.find(op.sidechain_address_id); - if (sidechain_address != idx.end()) { if (db().head_block_time() >= HARDFORK_SIDECHAIN_DELETE_TIME) { db().remove(*sidechain_address); diff --git a/libraries/chain/sidechain_transaction_evaluator.cpp b/libraries/chain/sidechain_transaction_evaluator.cpp index ba256199..b0d1a494 100644 --- a/libraries/chain/sidechain_transaction_evaluator.cpp +++ b/libraries/chain/sidechain_transaction_evaluator.cpp @@ -11,7 +11,7 @@ namespace graphene { namespace chain { void_result sidechain_transaction_create_evaluator::do_evaluate(const sidechain_transaction_create_operation &op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); - FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + FC_ASSERT(op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer."); FC_ASSERT((op.object_id.is() || op.object_id.is() || op.object_id.is()), "Invalid object id"); @@ -28,15 +28,26 @@ void_result sidechain_transaction_create_evaluator::do_evaluate(const sidechain_ object_id_type sidechain_transaction_create_evaluator::do_apply(const sidechain_transaction_create_operation &op) { try { const auto &new_sidechain_transaction_object = db().create([&](sidechain_transaction_object &sto) { + sto.timestamp = db().head_block_time(); sto.sidechain = op.sidechain; sto.object_id = op.object_id; sto.transaction = op.transaction; - sto.signers = op.signers; - std::transform(op.signers.begin(), op.signers.end(), std::inserter(sto.signatures, sto.signatures.end()), [](const son_info &si) { + std::vector signers; + for(const auto& signer : op.signers){ + son_sidechain_info ssi; + ssi.son_id = signer.son_id; + ssi.weight = signer.weight; + ssi.signing_key = signer.signing_key; + ssi.public_key = signer.sidechain_public_keys.at(op.sidechain); + signers.emplace_back(std::move(ssi)); + } + sto.signers = std::move(signers); + + std::transform(sto.signers.begin(), sto.signers.end(), std::inserter(sto.signatures, sto.signatures.end()), [](const son_sidechain_info &si) { return std::make_pair(si.son_id, std::string()); }); - for (const auto &si : op.signers) { + for (const auto &si : sto.signers) { sto.total_weight = sto.total_weight + si.weight; } sto.sidechain_transaction = ""; diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp index 776eb065..18be9a5b 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -39,13 +39,28 @@ void_result create_son_evaluator::do_evaluate(const son_create_operation& op) object_id_type create_son_evaluator::do_apply(const son_create_operation& op) { try { vote_id_type vote_id; - db().modify(db().get_global_properties(), [&vote_id](global_property_object& p) { - vote_id = get_next_vote_id(p, vote_id_type::son); - }); + flat_map vote_ids; - const auto& new_son_object = db().create( [&]( son_object& obj ){ + const auto now = db().head_block_time(); + if( now < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + db().modify(db().get_global_properties(), [&vote_id](global_property_object &p) { + vote_id = get_next_vote_id(p, vote_id_type::son_bitcoin); + }); + } + else { + db().modify(db().get_global_properties(), [&vote_ids](global_property_object &p) { + vote_ids[sidechain_type::bitcoin] = get_next_vote_id(p, vote_id_type::son_bitcoin); + vote_ids[sidechain_type::hive] = get_next_vote_id(p, vote_id_type::son_hive); + vote_ids[sidechain_type::ethereum] = get_next_vote_id(p, vote_id_type::son_ethereum); + }); + } + + const auto& new_son_object = db().create( [&]( son_object& obj ) { obj.son_account = op.owner_account; - obj.vote_id = vote_id; + if( now < HARDFORK_SON_FOR_ETHEREUM_TIME ) + obj.sidechain_vote_ids[sidechain_type::bitcoin] = vote_id; + else + obj.sidechain_vote_ids = vote_ids; obj.url = op.url; obj.deposit = op.deposit; obj.signing_key = op.signing_key; @@ -94,7 +109,8 @@ object_id_type update_son_evaluator::do_apply(const son_update_operation& op) if(op.new_signing_key.valid()) so.signing_key = *op.new_signing_key; if(op.new_sidechain_public_keys.valid()) so.sidechain_public_keys = *op.new_sidechain_public_keys; if(op.new_pay_vb.valid()) so.pay_vb = *op.new_pay_vb; - if(so.status == son_status::deregistered) so.status = son_status::inactive; + for(auto& status : so.statuses) + if(status.second == son_status::deregistered) status.second = son_status::inactive; }); } return op.son_id; @@ -127,7 +143,8 @@ void_result deregister_son_evaluator::do_apply(const son_deregister_operation& o }); db().modify(*son, [&op](son_object &so) { - so.status = son_status::deregistered; + for(auto& status : so.statuses) + status.second = son_status::deregistered; }); auto stats_obj = ss_idx.find(son->statistics); @@ -144,18 +161,28 @@ void_result son_heartbeat_evaluator::do_evaluate(const son_heartbeat_operation& { try { FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass const auto& idx = db().get_index_type().indices().get(); - auto itr = idx.find(op.son_id); + const auto itr = idx.find(op.son_id); FC_ASSERT( itr != idx.end() ); FC_ASSERT(itr->son_account == op.owner_account); auto stats = itr->statistics( db() ); // Inactive SONs need not send heartbeats - FC_ASSERT((itr->status == son_status::active) || (itr->status == son_status::in_maintenance) || (itr->status == son_status::request_maintenance), "Inactive SONs need not send heartbeats"); + bool status_need_to_send_heartbeats = false; + for(const auto& status : itr->statuses) + { + if( (status.second == son_status::active) || (status.second == son_status::in_maintenance) || (status.second == son_status::request_maintenance) ) + status_need_to_send_heartbeats = true; + } + FC_ASSERT(status_need_to_send_heartbeats, "Inactive SONs need not send heartbeats"); // Account for network delays fc::time_point_sec min_ts = db().head_block_time() - fc::seconds(5 * db().block_interval()); // Account for server ntp sync difference fc::time_point_sec max_ts = db().head_block_time() + fc::seconds(5 * db().block_interval()); - FC_ASSERT(op.ts > stats.last_active_timestamp, "Heartbeat sent without waiting minimum time"); - FC_ASSERT(op.ts > stats.last_down_timestamp, "Heartbeat sent is invalid can't be <= last down timestamp"); + for(const auto& active_sidechain_type : active_sidechain_types(db().head_block_time())) { + if(stats.last_active_timestamp.contains(active_sidechain_type)) + FC_ASSERT(op.ts > stats.last_active_timestamp.at(active_sidechain_type), "Heartbeat sent for sidechain = ${sidechain} without waiting minimum time", ("sidechain", active_sidechain_type)); + if(stats.last_down_timestamp.contains(active_sidechain_type)) + FC_ASSERT(op.ts > stats.last_down_timestamp.at(active_sidechain_type), "Heartbeat sent for sidechain = ${sidechain} is invalid can't be <= last down timestamp", ("sidechain", active_sidechain_type)); + } FC_ASSERT(op.ts >= min_ts, "Heartbeat ts is behind the min threshold"); FC_ASSERT(op.ts <= max_ts, "Heartbeat ts is above the max threshold"); return void_result(); @@ -164,44 +191,48 @@ void_result son_heartbeat_evaluator::do_evaluate(const son_heartbeat_operation& object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& op) { try { const auto& idx = db().get_index_type().indices().get(); - auto itr = idx.find(op.son_id); + const auto itr = idx.find(op.son_id); if(itr != idx.end()) { const global_property_object& gpo = db().get_global_properties(); - vector active_son_ids; - active_son_ids.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), - std::inserter(active_son_ids, active_son_ids.end()), - [](const son_info& swi) { - return swi.son_id; - }); - auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), op.son_id); - bool is_son_active = true; + for(const auto& active_sidechain_sons : gpo.active_sons) { + const auto& sidechain = active_sidechain_sons.first; + const auto& active_sons = active_sidechain_sons.second; - if(it_son == active_son_ids.end()) { - is_son_active = false; - } + vector active_son_ids; + active_son_ids.reserve(active_sons.size()); + std::transform(active_sons.cbegin(), active_sons.cend(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_sidechain_info &swi) { + return swi.son_id; + }); - if(itr->status == son_status::in_maintenance) { - db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) - { - sso.current_interval_downtime += op.ts.sec_since_epoch() - sso.last_down_timestamp.sec_since_epoch(); - sso.last_active_timestamp = op.ts; - } ); + const auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), op.son_id); + bool is_son_active = true; - db().modify(*itr, [&is_son_active](son_object &so) { - if(is_son_active) { - so.status = son_status::active; - } else { - so.status = son_status::inactive; - } - }); - } else if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { - db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) - { - sso.last_active_timestamp = op.ts; - } ); + if (it_son == active_son_ids.end()) { + is_son_active = false; + } + + if (itr->statuses.at(sidechain) == son_status::in_maintenance) { + db().modify(itr->statistics(db()), [&](son_statistics_object &sso) { + sso.current_interval_downtime[sidechain] += op.ts.sec_since_epoch() - (sso.last_down_timestamp.contains(sidechain) ? sso.last_down_timestamp.at(sidechain).sec_since_epoch() : op.ts.sec_since_epoch()); + sso.last_active_timestamp[sidechain] = op.ts; + }); + + db().modify(*itr, [&is_son_active, &sidechain](son_object &so) { + if (is_son_active) { + so.statuses[sidechain] = son_status::active; + } else { + so.statuses[sidechain] = son_status::inactive; + } + }); + } else if ((itr->statuses.at(sidechain) == son_status::active) || (itr->statuses.at(sidechain) == son_status::request_maintenance)) { + db().modify(itr->statistics(db()), [&](son_statistics_object &sso) { + sso.last_active_timestamp[sidechain] = op.ts; + }); + } } } return op.son_id; @@ -213,29 +244,41 @@ void_result son_report_down_evaluator::do_evaluate(const son_report_down_operati FC_ASSERT(op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer."); const auto& idx = db().get_index_type().indices().get(); FC_ASSERT( idx.find(op.son_id) != idx.end() ); - auto itr = idx.find(op.son_id); - auto stats = itr->statistics( db() ); - FC_ASSERT(itr->status == son_status::active || itr->status == son_status::request_maintenance, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down"); - FC_ASSERT(op.down_ts >= stats.last_active_timestamp, "down_ts should be greater than last_active_timestamp"); + const auto itr = idx.find(op.son_id); + const auto stats = itr->statistics( db() ); + bool status_need_to_report_down = false; + for(const auto& status : itr->statuses) + { + if( (status.second == son_status::active) || (status.second == son_status::request_maintenance) ) + status_need_to_report_down = true; + } + FC_ASSERT(status_need_to_report_down, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down"); + for(const auto& active_sidechain_type : active_sidechain_types(db().head_block_time())) { + if(stats.last_active_timestamp.contains(active_sidechain_type)) + FC_ASSERT(op.down_ts >= stats.last_active_timestamp.at(active_sidechain_type), "sidechain = ${sidechain} down_ts should be greater than last_active_timestamp", ("sidechain", active_sidechain_type)); + } return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } object_id_type son_report_down_evaluator::do_apply(const son_report_down_operation& op) { try { const auto& idx = db().get_index_type().indices().get(); - auto itr = idx.find(op.son_id); + const auto itr = idx.find(op.son_id); if(itr != idx.end()) { - if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { - db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) - { - sso.last_down_timestamp = op.down_ts; - }); + for( const auto& status : itr->statuses ) { + const auto& sidechain = status.first; - db().modify(*itr, [&op](son_object &so) { - so.status = son_status::in_maintenance; - }); - } + if ((status.second == son_status::active) || (status.second == son_status::request_maintenance)) { + db().modify(*itr, [&sidechain](son_object &so) { + so.statuses[sidechain] = son_status::in_maintenance; + }); + + db().modify(itr->statistics(db()), [&](son_statistics_object &sso) { + sso.last_down_timestamp[sidechain] = op.down_ts; + }); + } + } } return op.son_id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -249,9 +292,19 @@ void_result son_maintenance_evaluator::do_evaluate(const son_maintenance_operati FC_ASSERT( itr != idx.end() ); // Inactive SONs can't go to maintenance, toggle between active and request_maintenance states if(op.request_type == son_maintenance_request_type::request_maintenance) { - FC_ASSERT(itr->status == son_status::active, "Inactive SONs can't request for maintenance"); - } else if(op.request_type == son_maintenance_request_type::cancel_request_maintenance) { - FC_ASSERT(itr->status == son_status::request_maintenance, "Only maintenance requested SONs can cancel the request"); + bool status_active = false; + for(const auto& status : itr->statuses) { + if( (status.second == son_status::active) ) + status_active = true; + } + FC_ASSERT(status_active, "Inactive SONs can't request for maintenance"); + } else if(op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + bool status_request_maintenance = false; + for(const auto& status : itr->statuses) { + if( (status.second == son_status::request_maintenance) ) + status_request_maintenance = true; + } + FC_ASSERT(status_request_maintenance, "Only maintenance requested SONs can cancel the request"); } else { FC_ASSERT(false, "Invalid maintenance operation"); } @@ -264,15 +317,33 @@ object_id_type son_maintenance_evaluator::do_apply(const son_maintenance_operati auto itr = idx.find(op.son_id); if(itr != idx.end()) { - if(itr->status == son_status::active && op.request_type == son_maintenance_request_type::request_maintenance) { - db().modify(*itr, [](son_object &so) { - so.status = son_status::request_maintenance; - }); - } else if(itr->status == son_status::request_maintenance && op.request_type == son_maintenance_request_type::cancel_request_maintenance) { - db().modify(*itr, [](son_object &so) { - so.status = son_status::active; - }); - } + bool status_active = false; + for(const auto& status : itr->statuses) { + if( (status.second == son_status::active) ) + status_active = true; + } + if(status_active && op.request_type == son_maintenance_request_type::request_maintenance) { + db().modify(*itr, [](son_object &so) { + for(auto& status : so.statuses) { + status.second = son_status::request_maintenance; + } + }); + } + else + { + bool status_request_maintenance = false; + for(const auto& status : itr->statuses) { + if( (status.second == son_status::request_maintenance) ) + status_request_maintenance = true; + } + if(status_request_maintenance && op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + db().modify(*itr, [](son_object &so) { + for(auto& status : so.statuses) { + status.second = son_status::active; + } + }); + } + } } return op.son_id; } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/son_object.cpp b/libraries/chain/son_object.cpp index c94a6250..717285ae 100644 --- a/libraries/chain/son_object.cpp +++ b/libraries/chain/son_object.cpp @@ -6,20 +6,22 @@ namespace graphene { namespace chain { db.adjust_balance(son_account, pay); } - bool son_object::has_valid_config()const { - return ((std::string(signing_key).length() > 0) && - (sidechain_public_keys.size() > 0) && - (sidechain_public_keys.find( sidechain_type::bitcoin ) != sidechain_public_keys.end()) && - (sidechain_public_keys.at(sidechain_type::bitcoin).length() > 0)); + bool son_object::has_valid_config(sidechain_type sidechain) const { + return (sidechain_public_keys.find( sidechain ) != sidechain_public_keys.end()) && + (sidechain_public_keys.at(sidechain).length() > 0); } - bool son_object::has_valid_config(time_point_sec head_block_time)const { - bool retval = has_valid_config(); + bool son_object::has_valid_config(time_point_sec head_block_time, sidechain_type sidechain) const { + bool retval = (std::string(signing_key).length() > 0) && (sidechain_public_keys.size() > 0); - if (head_block_time >= HARDFORK_SON_FOR_HIVE_TIME) { - retval = retval && - (sidechain_public_keys.find( sidechain_type::hive ) != sidechain_public_keys.end()) && - (sidechain_public_keys.at(sidechain_type::hive).length() > 0); + if (head_block_time < HARDFORK_SON_FOR_HIVE_TIME) { + retval = retval && has_valid_config(sidechain_type::bitcoin); + } + if (head_block_time >= HARDFORK_SON_FOR_HIVE_TIME && head_block_time < HARDFORK_SON_FOR_ETHEREUM_TIME) { + retval = retval && has_valid_config(sidechain_type::bitcoin) && has_valid_config(sidechain_type::hive); + } + else if (head_block_time >= HARDFORK_SON_FOR_ETHEREUM_TIME) { + retval = retval && has_valid_config(sidechain); } return retval; diff --git a/libraries/chain/son_wallet_deposit_evaluator.cpp b/libraries/chain/son_wallet_deposit_evaluator.cpp index f9620282..8d0199d4 100644 --- a/libraries/chain/son_wallet_deposit_evaluator.cpp +++ b/libraries/chain/son_wallet_deposit_evaluator.cpp @@ -23,9 +23,9 @@ void_result create_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_de const auto &swdo_idx = db().get_index_type().indices().get(); const auto swdo = swdo_idx.find(op.sidechain_uid); if (swdo == swdo_idx.end()) { - auto &gpo = db().get_global_properties(); + const auto &gpo = db().get_global_properties(); bool expected = false; - for (auto &si : gpo.active_sons) { + for (auto &si : gpo.active_sons.at(op.sidechain)) { if (op.son_id == si.son_id) { expected = true; break; @@ -78,8 +78,8 @@ object_id_type create_son_wallet_deposit_evaluator::do_apply(const son_wallet_de swdo.peerplays_to = op.peerplays_to; swdo.peerplays_asset = op.peerplays_asset; - auto &gpo = db().get_global_properties(); - for (auto &si : gpo.active_sons) { + const auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons.at(op.sidechain)) { swdo.expected_reports.insert(std::make_pair(si.son_id, si.weight)); auto stats_itr = db().get_index_type().indices().get().find(si.son_id); @@ -142,11 +142,11 @@ void_result process_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_d { try{ FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); - FC_ASSERT(db().get_global_properties().active_sons.size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); const auto& idx = db().get_index_type().indices().get(); const auto& itr = idx.find(op.son_wallet_deposit_id); FC_ASSERT(itr != idx.end(), "Son wallet deposit not found"); + FC_ASSERT(db().get_global_properties().active_sons.at(itr->sidechain).size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); FC_ASSERT(!itr->processed, "Son wallet deposit is already processed"); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/chain/son_wallet_evaluator.cpp b/libraries/chain/son_wallet_evaluator.cpp index 0baed1cb..03e2edc1 100644 --- a/libraries/chain/son_wallet_evaluator.cpp +++ b/libraries/chain/son_wallet_evaluator.cpp @@ -7,8 +7,9 @@ namespace graphene { namespace chain { void_result recreate_son_wallet_evaluator::do_evaluate(const son_wallet_recreate_operation& op) { try{ - FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); - FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + const auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT(op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer."); const auto& idx = db().get_index_type().indices().get(); auto itr = idx.rbegin(); @@ -16,12 +17,36 @@ void_result recreate_son_wallet_evaluator::do_evaluate(const son_wallet_recreate { // Compare current wallet SONs and to-be lists of active sons auto cur_wallet_sons = (*itr).sons; - auto new_wallet_sons = op.sons; + flat_map > new_wallet_sons; + if( now < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + for(const auto& son : op.sons){ + for(const auto& active_sidechain_type : active_sidechain_types(db().head_block_time())){ + son_sidechain_info ssi; + ssi.son_id = son.son_id; + ssi.weight = son.weight; + ssi.signing_key = son.signing_key; + ssi.public_key = son.sidechain_public_keys.at(active_sidechain_type); + new_wallet_sons[active_sidechain_type].emplace_back(std::move(ssi)); + } + } + } + else{ + FC_ASSERT(op.extensions.value.sidechain_sons.valid(), "Sons is not valid"); + new_wallet_sons = *op.extensions.value.sidechain_sons; + } bool son_sets_equal = (cur_wallet_sons.size() == new_wallet_sons.size()); if (son_sets_equal) { - for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { - son_sets_equal = son_sets_equal && cur_wallet_sons.at(i) == new_wallet_sons.at(i); + for( const auto& cur_wallet_sidechain_sons : cur_wallet_sons ) { + const auto& sidechain = cur_wallet_sidechain_sons.first; + const auto& _cur_wallet_sidechain_sons = cur_wallet_sidechain_sons.second; + + son_sets_equal = son_sets_equal && (_cur_wallet_sidechain_sons.size() == new_wallet_sons.at(sidechain).size()); + if (son_sets_equal) { + for (size_t i = 0; i < cur_wallet_sons.size(); i++) { + son_sets_equal = son_sets_equal && _cur_wallet_sidechain_sons.at(i) == new_wallet_sons.at(sidechain).at(i); + } + } } } @@ -43,9 +68,26 @@ object_id_type recreate_son_wallet_evaluator::do_apply(const son_wallet_recreate } const auto& new_son_wallet_object = db().create( [&]( son_wallet_object& obj ){ - obj.valid_from = db().head_block_time(); + const auto now = db().head_block_time(); + obj.valid_from = now; obj.expires = time_point_sec::maximum(); - obj.sons = op.sons; + if( now < HARDFORK_SON_FOR_ETHEREUM_TIME ) { + flat_map > sons; + for(const auto& son : op.sons){ + for(const auto& active_sidechain_type : active_sidechain_types(db().head_block_time())){ + son_sidechain_info ssi; + ssi.son_id = son.son_id; + ssi.weight = son.weight; + ssi.signing_key = son.signing_key; + ssi.public_key = son.sidechain_public_keys.at(active_sidechain_type); + sons[active_sidechain_type].emplace_back(std::move(ssi)); + } + } + obj.sons = std::move(sons); + } + else{ + obj.sons = *op.extensions.value.sidechain_sons; + } }); return new_son_wallet_object.id; } FC_CAPTURE_AND_RETHROW( (op) ) } @@ -55,8 +97,19 @@ void_result update_son_wallet_evaluator::do_evaluate(const son_wallet_update_ope FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + const son_wallet_id_type son_wallet_id = [&]{ + if(db().head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) + { + const auto ast = active_sidechain_types(db().head_block_time()); + const auto id = (op.son_wallet_id.instance.value - std::distance(ast.begin(), ast.find(op.sidechain))) / ast.size(); + return son_wallet_id_type{ id }; + } + + return op.son_wallet_id; + }(); + const auto& idx = db().get_index_type().indices().get(); - FC_ASSERT( idx.find(op.son_wallet_id) != idx.end() ); + FC_ASSERT( idx.find(son_wallet_id) != idx.end() ); //auto itr = idx.find(op.son_wallet_id); //FC_ASSERT( itr->addresses.find(op.sidechain) == itr->addresses.end() || // itr->addresses.at(op.sidechain).empty(), "Sidechain wallet address already set"); @@ -65,8 +118,19 @@ void_result update_son_wallet_evaluator::do_evaluate(const son_wallet_update_ope object_id_type update_son_wallet_evaluator::do_apply(const son_wallet_update_operation& op) { try { + const son_wallet_id_type son_wallet_id = [&]{ + if(db().head_block_time() >= HARDFORK_SON_FOR_ETHEREUM_TIME) + { + const auto ast = active_sidechain_types(db().head_block_time()); + const auto id = (op.son_wallet_id.instance.value - std::distance(ast.begin(), ast.find(op.sidechain))) / ast.size(); + return son_wallet_id_type{ id }; + } + + return op.son_wallet_id; + }(); + const auto& idx = db().get_index_type().indices().get(); - auto itr = idx.find(op.son_wallet_id); + auto itr = idx.find(son_wallet_id); if (itr != idx.end()) { if (itr->addresses.find(op.sidechain) == itr->addresses.end()) { diff --git a/libraries/chain/son_wallet_withdraw_evaluator.cpp b/libraries/chain/son_wallet_withdraw_evaluator.cpp index 2110e49d..7101c5fa 100644 --- a/libraries/chain/son_wallet_withdraw_evaluator.cpp +++ b/libraries/chain/son_wallet_withdraw_evaluator.cpp @@ -10,12 +10,13 @@ namespace graphene { namespace chain { void_result create_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_create_operation& op) { try { - FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); - + const auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); const auto &son_idx = db().get_index_type().indices().get(); const auto so = son_idx.find(op.son_id); FC_ASSERT(so != son_idx.end(), "SON not found"); FC_ASSERT(so->son_account == op.payer, "Payer is not SON account owner"); + FC_ASSERT(!(op.sidechain == sidechain_type::peerplays && now >= HARDFORK_SON_FOR_ETHEREUM_TIME), "Peerplays sidechain type is not allowed"); const auto &ss_idx = db().get_index_type().indices().get(); FC_ASSERT(ss_idx.find(op.son_id) != ss_idx.end(), "Statistic object for a given SON ID does not exists"); @@ -23,15 +24,23 @@ void_result create_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_w const auto &swwo_idx = db().get_index_type().indices().get(); const auto swwo = swwo_idx.find(op.peerplays_uid); if (swwo == swwo_idx.end()) { - auto &gpo = db().get_global_properties(); + const sidechain_type sidechain = [&op]{ + if(op.sidechain == sidechain_type::peerplays){ + return op.withdraw_sidechain; + } + else + return op.sidechain; + }(); + + const auto &gpo = db().get_global_properties(); bool expected = false; - for (auto &si : gpo.active_sons) { + for (auto &si : gpo.active_sons.at(sidechain)) { if (op.son_id == si.son_id) { expected = true; break; } } - FC_ASSERT(expected, "Only active SON can create deposit"); + FC_ASSERT(expected, "Only active SON can create withdraw"); } else { bool exactly_the_same = true; exactly_the_same = exactly_the_same && (swwo->sidechain == op.sidechain); @@ -76,8 +85,16 @@ object_id_type create_son_wallet_withdraw_evaluator::do_apply(const son_wallet_w swwo.withdraw_currency = op.withdraw_currency; swwo.withdraw_amount = op.withdraw_amount; - auto &gpo = db().get_global_properties(); - for (auto &si : gpo.active_sons) { + const sidechain_type sidechain = [&op]{ + if(op.sidechain == sidechain_type::peerplays){ + return op.withdraw_sidechain; + } + else + return op.sidechain; + }(); + + const auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons.at(sidechain)) { swwo.expected_reports.insert(std::make_pair(si.son_id, si.weight)); auto stats_itr = db().get_index_type().indices().get().find(si.son_id); @@ -138,13 +155,17 @@ object_id_type create_son_wallet_withdraw_evaluator::do_apply(const son_wallet_w void_result process_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_process_operation& op) { try{ - FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + const auto now = db().head_block_time(); + FC_ASSERT(now >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); - FC_ASSERT(db().get_global_properties().active_sons.size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); const auto& idx = db().get_index_type().indices().get(); const auto& itr = idx.find(op.son_wallet_withdraw_id); FC_ASSERT(itr != idx.end(), "Son wallet withdraw not found"); + FC_ASSERT(!(itr->sidechain == sidechain_type::peerplays && now >= HARDFORK_SON_FOR_ETHEREUM_TIME), "Peerplays sidechain type is not allowed"); + if(itr->sidechain != sidechain_type::peerplays) { + FC_ASSERT(db().get_global_properties().active_sons.at(itr->sidechain).size() >= db().get_chain_properties().immutable_parameters.min_son_count, "Min required voted SONs not present"); + } FC_ASSERT(!itr->processed, "Son wallet withdraw is already processed"); return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } diff --git a/libraries/db/include/graphene/db/object_database.hpp b/libraries/db/include/graphene/db/object_database.hpp index fa2109aa..e76e5a83 100644 --- a/libraries/db/include/graphene/db/object_database.hpp +++ b/libraries/db/include/graphene/db/object_database.hpp @@ -29,6 +29,7 @@ #include #include +#include namespace graphene { namespace db { @@ -144,6 +145,7 @@ namespace graphene { namespace db { fc::path get_data_dir()const { return _data_dir; } /** public for testing purposes only... should be private in practice. */ + mutable std::mutex _undo_db_mutex; undo_database _undo_db; protected: template diff --git a/libraries/fc b/libraries/fc index e7369949..156b0c4e 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit e7369949bea26f3201d8442ba78286a88df74762 +Subproject commit 156b0c4e41c9215eadb2af8009b05e0f38c16dda diff --git a/libraries/net/core_messages.cpp b/libraries/net/core_messages.cpp index efff812d..3a4b842c 100644 --- a/libraries/net/core_messages.cpp +++ b/libraries/net/core_messages.cpp @@ -47,4 +47,3 @@ namespace graphene { namespace net { const core_message_type_enum get_current_connections_reply_message::type = core_message_type_enum::get_current_connections_reply_message_type; } } // graphene::net - diff --git a/libraries/net/include/graphene/net/config.hpp b/libraries/net/include/graphene/net/config.hpp index 9edca51c..894850d6 100644 --- a/libraries/net/include/graphene/net/config.hpp +++ b/libraries/net/include/graphene/net/config.hpp @@ -23,6 +23,8 @@ */ #pragma once +#include + #define GRAPHENE_NET_PROTOCOL_VERSION 106 /** @@ -110,3 +112,6 @@ #define GRAPHENE_NET_MAX_NESTED_OBJECTS (250) #define MAXIMUM_PEERDB_SIZE 1000 + +constexpr size_t MAX_BLOCKS_TO_HANDLE_AT_ONCE = 200; +constexpr size_t MAX_SYNC_BLOCKS_TO_PREFETCH = 10 * MAX_BLOCKS_TO_HANDLE_AT_ONCE; diff --git a/libraries/net/include/graphene/net/node.hpp b/libraries/net/include/graphene/net/node.hpp index e17af148..cbf5c594 100644 --- a/libraries/net/include/graphene/net/node.hpp +++ b/libraries/net/include/graphene/net/node.hpp @@ -61,7 +61,7 @@ namespace graphene { namespace net { class node_delegate { public: - virtual ~node_delegate(){} + virtual ~node_delegate() = default; /** * If delegate has the item, the network has no need to fetch it. @@ -71,7 +71,9 @@ namespace graphene { namespace net { /** * @brief Called when a new block comes in from the network * + * @param blk_msg the message which contains the block * @param sync_mode true if the message was fetched through the sync process, false during normal operation + * @param contained_transaction_msg_ids container for the transactions to write back into * @returns true if this message caused the blockchain to switch forks, false if it did not * * @throws exception if error validating the item, otherwise the item is @@ -152,6 +154,8 @@ namespace graphene { namespace net { virtual uint32_t get_block_number(const item_hash_t& block_id) = 0; + virtual fc::time_point_sec get_last_known_hardfork_time() = 0; + /** * Returns the time a block was produced (if block_id = 0, returns genesis time). * If we don't know about the block, returns time_point_sec::min() @@ -193,7 +197,7 @@ namespace graphene { namespace net { { public: node(const std::string& user_agent); - ~node(); + virtual ~node(); void close(); @@ -211,11 +215,34 @@ namespace graphene { namespace net { */ void add_node( const fc::ip::endpoint& ep ); + /***** + * @brief add a list of nodes to seed the p2p network + * @param seeds a vector of url strings + */ + void add_seed_nodes( std::vector seeds ); + + /**** + * @brief add a node to seed the p2p network + * @param in the url as a string + */ + void add_seed_node( const std::string& in); + /** * Attempt to connect to the specified endpoint immediately. */ virtual void connect_to_endpoint( const fc::ip::endpoint& ep ); + /** + * @brief Helper to convert a string to a collection of endpoints + * + * This converts a string (i.e. "bitshares.eu:665535" to a collection of endpoints. + * NOTE: Throws an exception if not in correct format or was unable to resolve URL. + * + * @param in the incoming string + * @returns a vector of endpoints + */ + static std::vector resolve_string_to_ip_endpoints( const std::string& in ); + /** * Specifies the network interface and port upon which incoming * connections should be accepted. diff --git a/libraries/net/include/graphene/net/peer_connection.hpp b/libraries/net/include/graphene/net/peer_connection.hpp index 61f1cef5..58c467a6 100644 --- a/libraries/net/include/graphene/net/peer_connection.hpp +++ b/libraries/net/include/graphene/net/peer_connection.hpp @@ -62,6 +62,7 @@ namespace graphene { namespace net class peer_connection_delegate { public: + virtual ~peer_connection_delegate() = default; virtual void on_message(peer_connection* originating_peer, const message& received_message) = 0; virtual void on_connection_closed(peer_connection* originating_peer) = 0; @@ -125,7 +126,7 @@ namespace graphene { namespace net * it is sitting on the queue */ virtual size_t get_size_in_queue() = 0; - virtual ~queued_message() {} + virtual ~queued_message() = default; }; /* when you queue up a 'real_queued_message', a full copy of the message is @@ -258,6 +259,8 @@ namespace graphene { namespace net uint32_t last_known_fork_block_number = 0; + fc::time_point_sec last_known_hardfork_time; + fc::future accept_or_connect_task_done; firewall_check_state_data *firewall_check_state = nullptr; diff --git a/libraries/net/include/graphene/net/peer_database.hpp b/libraries/net/include/graphene/net/peer_database.hpp index ff7f4036..3aadf599 100644 --- a/libraries/net/include/graphene/net/peer_database.hpp +++ b/libraries/net/include/graphene/net/peer_database.hpp @@ -97,7 +97,7 @@ namespace graphene { namespace net { { public: peer_database(); - ~peer_database(); + virtual ~peer_database(); void open(const fc::path& databaseFilename); void close(); diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index 6ea68832..c3198d1d 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -21,6 +21,7 @@ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ +#include #include #include #include @@ -71,6 +72,7 @@ #include #include #include +#include #include #include @@ -126,6 +128,124 @@ namespace graphene { namespace net { namespace detail { namespace bmi = boost::multi_index; + + /******* + * A class to wrap std::unordered_set for multithreading + */ + template , class Pred = std::equal_to > + class concurrent_unordered_set : private std::unordered_set + { + private: + mutable fc::mutex mux; + + public: + /// Iterations require a lock. This exposes the mutex. Use with care (i.e. lock_guard) + fc::mutex& get_mutex()const { return mux; } + + /// Insertion + /// @{ + std::pair< typename std::unordered_set::iterator, bool> emplace( Key key) + { + fc::scoped_lock lock(mux); + return std::unordered_set::emplace( key ); + } + std::pair< typename std::unordered_set::iterator, bool> insert (const Key& val) + { + fc::scoped_lock lock(mux); + return std::unordered_set::insert( val ); + } + /// @} + /// Size + /// @{ + size_t size() const + { + fc::scoped_lock lock(mux); + return std::unordered_set::size(); + } + bool empty() const noexcept + { + fc::scoped_lock lock(mux); + return std::unordered_set::empty(); + } + /// @} + /// Removal + /// @{ + void clear() noexcept + { + fc::scoped_lock lock(mux); + std::unordered_set::clear(); + } + typename std::unordered_set::iterator erase( + typename std::unordered_set::const_iterator itr) + { + fc::scoped_lock lock(mux); + return std::unordered_set::erase( itr); + } + size_t erase( const Key& key) + { + fc::scoped_lock lock(mux); + return std::unordered_set::erase( key ); + } + /// @} + /// Swap + /// @{ + void swap( typename std::unordered_set& other ) noexcept + { + fc::scoped_lock lock(mux); + std::unordered_set::swap( other ); + } + /// @} + /// Iteration + /// @{ + typename std::unordered_set::iterator begin() noexcept + { + fc::scoped_lock lock(mux); + return std::unordered_set::begin(); + } + typename std::unordered_set::const_iterator begin() const noexcept + { + fc::scoped_lock lock(mux); + return std::unordered_set::begin(); + } + typename std::unordered_set::local_iterator begin(size_t n) + { + fc::scoped_lock lock(mux); + return std::unordered_set::begin(n); + } + typename std::unordered_set::const_local_iterator begin(size_t n) const + { + fc::scoped_lock lock(mux); + return std::unordered_set::begin(n); + } + typename std::unordered_set::iterator end() noexcept + { + fc::scoped_lock lock(mux); + return std::unordered_set::end(); + } + typename std::unordered_set::const_iterator end() const noexcept + { + fc::scoped_lock lock(mux); + return std::unordered_set::end(); + } + typename std::unordered_set::local_iterator end(size_t n) + { + fc::scoped_lock lock(mux); + return std::unordered_set::end(n); + } + typename std::unordered_set::const_local_iterator end(size_t n) const + { + fc::scoped_lock lock(mux); + return std::unordered_set::end(n); + } + /// @} + /// Search + typename std::unordered_set::const_iterator find(Key key) + { + fc::scoped_lock lock(mux); + return std::unordered_set::find(key); + } + }; + class blockchain_tied_message_cache { private: @@ -298,6 +418,7 @@ namespace graphene { namespace net { namespace detail { (sync_status) \ (connection_count_changed) \ (get_block_number) \ + (get_last_known_hardfork_time) \ (get_block_time) \ (get_head_block_id) \ (estimate_last_known_fork_from_git_revision_timestamp) \ @@ -397,6 +518,7 @@ namespace graphene { namespace net { namespace detail { void sync_status( uint32_t item_type, uint32_t item_count ) override; void connection_count_changed( uint32_t c ) override; uint32_t get_block_number(const item_hash_t& block_id) override; + fc::time_point_sec get_last_known_hardfork_time() override; fc::time_point_sec get_block_time(const item_hash_t& block_id) override; item_hash_t get_head_block_id() const override; uint32_t estimate_last_known_fork_from_git_revision_timestamp(uint32_t unix_timestamp) const override; @@ -477,9 +599,9 @@ namespace graphene { namespace net { namespace detail { /// used by the task that advertises inventory during normal operation // @{ - fc::promise::ptr _retrigger_advertise_inventory_loop_promise; - fc::future _advertise_inventory_loop_done; - std::unordered_set _new_inventory; /// list of items we have received but not yet advertised to our peers + fc::promise::ptr _retrigger_advertise_inventory_loop_promise; + fc::future _advertise_inventory_loop_done; + concurrent_unordered_set _new_inventory; /// list of items we have received but not yet advertised to our peers // @} fc::future _terminate_inactive_connections_loop_done; @@ -515,13 +637,13 @@ namespace graphene { namespace net { namespace detail { /** Stores all connections which have not yet finished key exchange or are still sending initial handshaking messages * back and forth (not yet ready to initiate syncing) */ - std::unordered_set _handshaking_connections; + concurrent_unordered_set _handshaking_connections; /** stores fully established connections we're either syncing with or in normal operation with */ - std::unordered_set _active_connections; + concurrent_unordered_set _active_connections; /** stores connections we've closed (sent closing message, not actually closed), but are still waiting for the remote end to close before we delete them */ - std::unordered_set _closing_connections; + concurrent_unordered_set _closing_connections; /** stores connections we've closed, but are still waiting for the OS to notify us that the socket is really closed */ - std::unordered_set _terminating_connections; + concurrent_unordered_set _terminating_connections; boost::circular_buffer _most_recent_blocks_accepted; // the /n/ most recent blocks we've accepted (currently tuned to the max number of connections) @@ -552,6 +674,10 @@ namespace graphene { namespace net { namespace detail { fc::future _bandwidth_monitor_loop_done; fc::future _dump_node_status_task_done; + /// Used by the task that checks whether addresses of seed nodes have been updated + /// @{ + boost::container::flat_set _seed_nodes; + fc::future _update_seed_nodes_loop_done; /* We have two alternate paths through the schedule_peer_for_deletion code -- one that * uses a mutex to prevent one fiber from adding items to the queue while another is deleting @@ -572,7 +698,7 @@ namespace graphene { namespace net { namespace detail { std::set _allowed_peers; #endif // ENABLE_P2P_DEBUGGING_API - bool _node_is_shutting_down; // set to true when we begin our destructor, used to prevent us from starting new tasks while we're shutting down + std::atomic_bool _node_is_shutting_down {false}; unsigned _maximum_number_of_blocks_to_handle_at_one_time; unsigned _maximum_number_of_sync_blocks_to_prefetch; @@ -725,6 +851,11 @@ namespace graphene { namespace net { namespace detail { void listen_to_p2p_network(); void connect_to_p2p_network(); void add_node( const fc::ip::endpoint& ep ); + void add_seed_node( const std::string& in); + void add_seed_nodes( std::vector seeds ); + void resolve_seed_node_and_add( const std::string& seed_string ); + void update_seed_nodes_task(); + void schedule_next_update_seed_nodes_task(); void initiate_connect_to(const peer_connection_ptr& peer); void connect_to_endpoint(const fc::ip::endpoint& ep); void listen_on_endpoint(const fc::ip::endpoint& ep , bool wait_if_not_available); @@ -823,7 +954,6 @@ namespace graphene { namespace net { namespace detail { _average_network_write_speed_hours(72), _average_network_usage_second_counter(0), _average_network_usage_minute_counter(0), - _node_is_shutting_down(false), _maximum_number_of_blocks_to_handle_at_one_time(MAXIMUM_NUMBER_OF_BLOCKS_TO_HANDLE_AT_ONE_TIME), _maximum_number_of_sync_blocks_to_prefetch(MAXIMUM_NUMBER_OF_BLOCKS_TO_PREFETCH), _maximum_blocks_per_peer_during_syncing(GRAPHENE_NET_MAX_BLOCKS_PER_PEER_DURING_SYNCING) @@ -840,18 +970,21 @@ namespace graphene { namespace net { namespace detail { { VERIFY_CORRECT_THREAD(); ilog( "cleaning up node" ); - _node_is_shutting_down = true; + _node_is_shutting_down.store(true); - for (const peer_connection_ptr& active_peer : _active_connections) { - fc::optional inbound_endpoint = active_peer->get_endpoint_for_connecting(); - if (inbound_endpoint) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& active_peer : _active_connections) { - fc::optional updated_peer_record = _potential_peer_db.lookup_entry_for_endpoint(*inbound_endpoint); - if (updated_peer_record) + fc::optional inbound_endpoint = active_peer->get_endpoint_for_connecting(); + if (inbound_endpoint) { - updated_peer_record->last_seen_time = fc::time_point::now(); - _potential_peer_db.update_entry(*updated_peer_record); + fc::optional updated_peer_record = _potential_peer_db.lookup_entry_for_endpoint(*inbound_endpoint); + if (updated_peer_record) + { + updated_peer_record->last_seen_time = fc::time_point::now(); + _potential_peer_db.update_entry(*updated_peer_record); + } } } } @@ -948,6 +1081,11 @@ namespace graphene { namespace net { namespace detail { } display_current_connections(); + if(_node_is_shutting_down) + { + ilog("Breaking p2p_network_connect_loop loop because node is shutting down"); + break; + } // if we broke out of the while loop, that means either we have connected to enough nodes, or // we don't have any good candidates to connect to right now. @@ -1030,7 +1168,7 @@ namespace graphene { namespace net { namespace detail { void node_impl::fetch_sync_items_loop() { VERIFY_CORRECT_THREAD(); - while( !_fetch_sync_items_loop_done.canceled() ) + while( !_fetch_sync_items_loop_done.canceled() && !_node_is_shutting_down ) { _sync_items_to_fetch_updated = false; dlog( "beginning another iteration of the sync items loop" ); @@ -1044,6 +1182,7 @@ namespace graphene { namespace net { namespace detail { std::set sync_items_to_request; // for each idle peer that we're syncing with + fc::scoped_lock lock(_active_connections.get_mutex()); for( const peer_connection_ptr& peer : _active_connections ) { if( peer->we_need_sync_items_from_peer && @@ -1102,6 +1241,7 @@ namespace graphene { namespace net { namespace detail { bool node_impl::is_item_in_any_peers_inventory(const item_id& item) const { + fc::scoped_lock lock(_active_connections.get_mutex()); for( const peer_connection_ptr& peer : _active_connections ) { if (peer->inventory_peer_advertised_to_us.find(item) != peer->inventory_peer_advertised_to_us.end() ) @@ -1141,9 +1281,13 @@ namespace graphene { namespace net { namespace detail { fetch_messages_to_send_set items_by_peer; // initialize the fetch_messages_to_send with an empty set of items for all idle peers - for (const peer_connection_ptr& peer : _active_connections) - if (peer->idle()) - items_by_peer.insert(peer_and_items_to_fetch(peer)); + { + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { + if (peer->idle()) + items_by_peer.insert(peer_and_items_to_fetch(peer)); + } + } // now loop over all items we want to fetch for (auto item_iter = _items_to_fetch.begin(); item_iter != _items_to_fetch.end();) @@ -1245,56 +1389,59 @@ namespace graphene { namespace net { namespace detail { dlog("beginning an iteration of advertise inventory"); // swap inventory into local variable, clearing the node's copy std::unordered_set inventory_to_advertise; - inventory_to_advertise.swap(_new_inventory); + _new_inventory.swap(inventory_to_advertise); // process all inventory to advertise and construct the inventory messages we'll send // first, then send them all in a batch (to avoid any fiber interruption points while // we're computing the messages) std::list > inventory_messages_to_send; - for (const peer_connection_ptr& peer : _active_connections) { - // only advertise to peers who are in sync with us - idump((peer->peer_needs_sync_items_from_us)); - if( !peer->peer_needs_sync_items_from_us ) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { - std::map > items_to_advertise_by_type; - // don't send the peer anything we've already advertised to it - // or anything it has advertised to us - // group the items we need to send by type, because we'll need to send one inventory message per type - unsigned total_items_to_send_to_this_peer = 0; - idump((inventory_to_advertise)); - for (const item_id& item_to_advertise : inventory_to_advertise) + // only advertise to peers who are in sync with us + idump((peer->peer_needs_sync_items_from_us)); + if( !peer->peer_needs_sync_items_from_us ) { - auto adv_to_peer = peer->inventory_advertised_to_peer.find(item_to_advertise); - auto adv_to_us = peer->inventory_peer_advertised_to_us.find(item_to_advertise); + std::map > items_to_advertise_by_type; + // don't send the peer anything we've already advertised to it + // or anything it has advertised to us + // group the items we need to send by type, because we'll need to send one inventory message per type + unsigned total_items_to_send_to_this_peer = 0; + idump((inventory_to_advertise)); + for (const item_id& item_to_advertise : inventory_to_advertise) + { + auto adv_to_peer = peer->inventory_advertised_to_peer.find(item_to_advertise); + auto adv_to_us = peer->inventory_peer_advertised_to_us.find(item_to_advertise); - if (adv_to_peer == peer->inventory_advertised_to_peer.end() && - adv_to_us == peer->inventory_peer_advertised_to_us.end()) - { - items_to_advertise_by_type[item_to_advertise.item_type].push_back(item_to_advertise.item_hash); - peer->inventory_advertised_to_peer.insert(peer_connection::timestamped_item_id(item_to_advertise, fc::time_point::now())); - ++total_items_to_send_to_this_peer; - if (item_to_advertise.item_type == trx_message_type) - testnetlog("advertising transaction ${id} to peer ${endpoint}", ("id", item_to_advertise.item_hash)("endpoint", peer->get_remote_endpoint())); - dlog("advertising item ${id} to peer ${endpoint}", ("id", item_to_advertise.item_hash)("endpoint", peer->get_remote_endpoint())); - } - else - { - if (adv_to_peer != peer->inventory_advertised_to_peer.end() ) + if (adv_to_peer == peer->inventory_advertised_to_peer.end() && + adv_to_us == peer->inventory_peer_advertised_to_us.end()) + { + items_to_advertise_by_type[item_to_advertise.item_type].push_back(item_to_advertise.item_hash); + peer->inventory_advertised_to_peer.insert(peer_connection::timestamped_item_id(item_to_advertise, fc::time_point::now())); + ++total_items_to_send_to_this_peer; + if (item_to_advertise.item_type == trx_message_type) + testnetlog("advertising transaction ${id} to peer ${endpoint}", ("id", item_to_advertise.item_hash)("endpoint", peer->get_remote_endpoint())); + dlog("advertising item ${id} to peer ${endpoint}", ("id", item_to_advertise.item_hash)("endpoint", peer->get_remote_endpoint())); + } + else + { + if (adv_to_peer != peer->inventory_advertised_to_peer.end() ) idump( (*adv_to_peer) ); - if (adv_to_us != peer->inventory_peer_advertised_to_us.end() ) + if (adv_to_us != peer->inventory_peer_advertised_to_us.end() ) idump( (*adv_to_us) ); + } } - } dlog("advertising ${count} new item(s) of ${types} type(s) to peer ${endpoint}", ("count", total_items_to_send_to_this_peer) - ("types", items_to_advertise_by_type.size()) - ("endpoint", peer->get_remote_endpoint())); - for (auto items_group : items_to_advertise_by_type) - inventory_messages_to_send.push_back(std::make_pair(peer, item_ids_inventory_message(items_group.first, items_group.second))); + ("types", items_to_advertise_by_type.size()) + ("endpoint", peer->get_remote_endpoint())); + for (auto items_group : items_to_advertise_by_type) + inventory_messages_to_send.push_back(std::make_pair(peer, item_ids_inventory_message(items_group.first, items_group.second))); + } + peer->clear_old_inventory(); } - peer->clear_old_inventory(); } for (auto iter = inventory_messages_to_send.begin(); iter != inventory_messages_to_send.end(); ++iter) @@ -1337,31 +1484,36 @@ namespace graphene { namespace net { namespace detail { // reconnect with the rest of the network, or it might just futher isolate us. { // As usual, the first step is to walk through all our peers and figure out which - // peers need action (disconneting, sending keepalives, etc), then we walk through + // peers need action (disconnecting, sending keepalives, etc), then we walk through // those lists yielding at our leisure later. ASSERT_TASK_NOT_PREEMPTED(); uint32_t handshaking_timeout = _peer_inactivity_timeout; fc::time_point handshaking_disconnect_threshold = fc::time_point::now() - fc::seconds(handshaking_timeout); - for( const peer_connection_ptr handshaking_peer : _handshaking_connections ) - if( handshaking_peer->connection_initiation_time < handshaking_disconnect_threshold && - handshaking_peer->get_last_message_received_time() < handshaking_disconnect_threshold && - handshaking_peer->get_last_message_sent_time() < handshaking_disconnect_threshold ) + { + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for( const peer_connection_ptr handshaking_peer : _handshaking_connections ) { - wlog( "Forcibly disconnecting from handshaking peer ${peer} due to inactivity of at least ${timeout} seconds", - ( "peer", handshaking_peer->get_remote_endpoint() )("timeout", handshaking_timeout ) ); - wlog("Peer's negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}", - ("status", handshaking_peer->negotiation_status) - ("sent", handshaking_peer->get_total_bytes_sent()) - ("received", handshaking_peer->get_total_bytes_received())); - handshaking_peer->connection_closed_error = fc::exception(FC_LOG_MESSAGE(warn, "Terminating handshaking connection due to inactivity of ${timeout} seconds. Negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}", - ("peer", handshaking_peer->get_remote_endpoint()) - ("timeout", handshaking_timeout) - ("status", handshaking_peer->negotiation_status) - ("sent", handshaking_peer->get_total_bytes_sent()) - ("received", handshaking_peer->get_total_bytes_received()))); - peers_to_disconnect_forcibly.push_back( handshaking_peer ); + if( handshaking_peer->connection_initiation_time < handshaking_disconnect_threshold && + handshaking_peer->get_last_message_received_time() < handshaking_disconnect_threshold && + handshaking_peer->get_last_message_sent_time() < handshaking_disconnect_threshold ) + { + wlog( "Forcibly disconnecting from handshaking peer ${peer} due to inactivity of at least ${timeout} seconds", + ( "peer", handshaking_peer->get_remote_endpoint() )("timeout", handshaking_timeout ) ); + wlog("Peer's negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}", + ("status", handshaking_peer->negotiation_status) + ("sent", handshaking_peer->get_total_bytes_sent()) + ("received", handshaking_peer->get_total_bytes_received())); + handshaking_peer->connection_closed_error = fc::exception(FC_LOG_MESSAGE(warn, "Terminating handshaking connection due to inactivity of ${timeout} seconds. Negotiating status: ${status}, bytes sent: ${sent}, bytes received: ${received}", + ("peer", handshaking_peer->get_remote_endpoint()) + ("timeout", handshaking_timeout) + ("status", handshaking_peer->negotiation_status) + ("sent", handshaking_peer->get_total_bytes_sent()) + ("received", handshaking_peer->get_total_bytes_received()))); + peers_to_disconnect_forcibly.push_back( handshaking_peer ); + } } + } // timeout for any active peers is two block intervals uint32_t active_disconnect_timeout = 10 * _recent_block_interval_in_seconds; @@ -1381,94 +1533,103 @@ namespace graphene { namespace net { namespace detail { fc::time_point active_disconnect_threshold = fc::time_point::now() - fc::seconds(active_disconnect_timeout); fc::time_point active_send_keepalive_threshold = fc::time_point::now() - fc::seconds(active_send_keepalive_timeout); fc::time_point active_ignored_request_threshold = fc::time_point::now() - active_ignored_request_timeout; - for( const peer_connection_ptr& active_peer : _active_connections ) { - if( active_peer->connection_initiation_time < active_disconnect_threshold && - active_peer->get_last_message_received_time() < active_disconnect_threshold ) + fc::scoped_lock lock(_active_connections.get_mutex()); + for( const peer_connection_ptr& active_peer : _active_connections ) { - wlog( "Closing connection with peer ${peer} due to inactivity of at least ${timeout} seconds", - ( "peer", active_peer->get_remote_endpoint() )("timeout", active_disconnect_timeout ) ); - peers_to_disconnect_gently.push_back( active_peer ); - } - else - { - bool disconnect_due_to_request_timeout = false; - for (const peer_connection::item_to_time_map_type::value_type& item_and_time : active_peer->sync_items_requested_from_peer) - if (item_and_time.second < active_ignored_request_threshold) - { - wlog("Disconnecting peer ${peer} because they didn't respond to my request for sync item ${id}", - ("peer", active_peer->get_remote_endpoint())("id", item_and_time.first.item_hash)); - disconnect_due_to_request_timeout = true; - break; - } - if (!disconnect_due_to_request_timeout && - active_peer->item_ids_requested_from_peer && - active_peer->item_ids_requested_from_peer->get<1>() < active_ignored_request_threshold) - { - wlog("Disconnecting peer ${peer} because they didn't respond to my request for sync item ids after ${synopsis}", - ("peer", active_peer->get_remote_endpoint()) - ("synopsis", active_peer->item_ids_requested_from_peer->get<0>())); - disconnect_due_to_request_timeout = true; - } - if (!disconnect_due_to_request_timeout) - for (const peer_connection::item_to_time_map_type::value_type& item_and_time : active_peer->items_requested_from_peer) + if( active_peer->connection_initiation_time < active_disconnect_threshold && + active_peer->get_last_message_received_time() < active_disconnect_threshold ) + { + wlog( "Closing connection with peer ${peer} due to inactivity of at least ${timeout} seconds", + ( "peer", active_peer->get_remote_endpoint() )("timeout", active_disconnect_timeout ) ); + peers_to_disconnect_gently.push_back( active_peer ); + } + else + { + bool disconnect_due_to_request_timeout = false; + for (const peer_connection::item_to_time_map_type::value_type& item_and_time : active_peer->sync_items_requested_from_peer) if (item_and_time.second < active_ignored_request_threshold) { - wlog("Disconnecting peer ${peer} because they didn't respond to my request for item ${id}", - ("peer", active_peer->get_remote_endpoint())("id", item_and_time.first.item_hash)); + wlog("Disconnecting peer ${peer} because they didn't respond to my request for sync item ${id}", + ("peer", active_peer->get_remote_endpoint())("id", item_and_time.first.item_hash)); disconnect_due_to_request_timeout = true; break; } - if (disconnect_due_to_request_timeout) - { - // we should probably disconnect nicely and give them a reason, but right now the logic - // for rescheduling the requests only executes when the connection is fully closed, - // and we want to get those requests rescheduled as soon as possible - peers_to_disconnect_forcibly.push_back(active_peer); - } - else if (active_peer->connection_initiation_time < active_send_keepalive_threshold && - active_peer->get_last_message_received_time() < active_send_keepalive_threshold) - { - wlog( "Sending a keepalive message to peer ${peer} who hasn't sent us any messages in the last ${timeout} seconds", - ( "peer", active_peer->get_remote_endpoint() )("timeout", active_send_keepalive_timeout ) ); - peers_to_send_keep_alive.push_back(active_peer); - } - else if (active_peer->we_need_sync_items_from_peer && - !active_peer->is_currently_handling_message() && - !active_peer->item_ids_requested_from_peer && - active_peer->ids_of_items_to_get.empty()) - { - // This is a state we should never get into in the first place, but if we do, we should disconnect the peer - // to re-establish the connection. - fc_wlog(fc::logger::get("sync"), "Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.", - ("peer", active_peer->get_remote_endpoint())); - wlog("Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.", - ("peer", active_peer->get_remote_endpoint())); - peers_to_disconnect_forcibly.push_back(active_peer); + if (!disconnect_due_to_request_timeout && + active_peer->item_ids_requested_from_peer && + active_peer->item_ids_requested_from_peer->get<1>() < active_ignored_request_threshold) + { + wlog("Disconnecting peer ${peer} because they didn't respond to my request for sync item ids after ${synopsis}", + ("peer", active_peer->get_remote_endpoint()) + ("synopsis", active_peer->item_ids_requested_from_peer->get<0>())); + disconnect_due_to_request_timeout = true; + } + if (!disconnect_due_to_request_timeout) + for (const peer_connection::item_to_time_map_type::value_type& item_and_time : active_peer->items_requested_from_peer) + if (item_and_time.second < active_ignored_request_threshold) + { + wlog("Disconnecting peer ${peer} because they didn't respond to my request for item ${id}", + ("peer", active_peer->get_remote_endpoint())("id", item_and_time.first.item_hash)); + disconnect_due_to_request_timeout = true; + break; + } + if (disconnect_due_to_request_timeout) + { + // we should probably disconnect nicely and give them a reason, but right now the logic + // for rescheduling the requests only executes when the connection is fully closed, + // and we want to get those requests rescheduled as soon as possible + peers_to_disconnect_forcibly.push_back(active_peer); + } + else if (active_peer->connection_initiation_time < active_send_keepalive_threshold && + active_peer->get_last_message_received_time() < active_send_keepalive_threshold) + { + wlog( "Sending a keepalive message to peer ${peer} who hasn't sent us any messages in the last ${timeout} seconds", + ( "peer", active_peer->get_remote_endpoint() )("timeout", active_send_keepalive_timeout ) ); + peers_to_send_keep_alive.push_back(active_peer); + } + else if (active_peer->we_need_sync_items_from_peer && + !active_peer->is_currently_handling_message() && + !active_peer->item_ids_requested_from_peer && + active_peer->ids_of_items_to_get.empty()) + { + // This is a state we should never get into in the first place, but if we do, we should disconnect the peer + // to re-establish the connection. + fc_wlog(fc::logger::get("sync"), "Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.", + ("peer", active_peer->get_remote_endpoint())); + wlog("Disconnecting peer ${peer} because we think we need blocks from them but sync has stalled.", + ("peer", active_peer->get_remote_endpoint())); + peers_to_disconnect_forcibly.push_back(active_peer); + } } } } fc::time_point closing_disconnect_threshold = fc::time_point::now() - fc::seconds(GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT); - for( const peer_connection_ptr& closing_peer : _closing_connections ) - if( closing_peer->connection_closed_time < closing_disconnect_threshold ) - { - // we asked this peer to close their connectoin to us at least GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT - // seconds ago, but they haven't done it yet. Terminate the connection now - wlog( "Forcibly disconnecting peer ${peer} who failed to close their connection in a timely manner", - ( "peer", closing_peer->get_remote_endpoint() ) ); - peers_to_disconnect_forcibly.push_back( closing_peer ); + { + fc::scoped_lock lock(_closing_connections.get_mutex()); + for( const peer_connection_ptr& closing_peer : _closing_connections ) { + if (closing_peer->connection_closed_time < closing_disconnect_threshold) { + // we asked this peer to close their connectoin to us at least GRAPHENE_NET_PEER_DISCONNECT_TIMEOUT + // seconds ago, but they haven't done it yet. Terminate the connection now + wlog("Forcibly disconnecting peer ${peer} who failed to close their connection in a timely manner", + ("peer", closing_peer->get_remote_endpoint())); + peers_to_disconnect_forcibly.push_back(closing_peer); + } } + } uint32_t failed_terminate_timeout_seconds = 120; fc::time_point failed_terminate_threshold = fc::time_point::now() - fc::seconds(failed_terminate_timeout_seconds); - for (const peer_connection_ptr& peer : _terminating_connections ) - if (peer->get_connection_terminated_time() != fc::time_point::min() && - peer->get_connection_terminated_time() < failed_terminate_threshold) - { - wlog("Terminating connection with peer ${peer}, closing the connection didn't work", ("peer", peer->get_remote_endpoint())); - peers_to_terminate.push_back(peer); + { + fc::scoped_lock lock(_terminating_connections.get_mutex()); + for (const peer_connection_ptr& peer : _terminating_connections ) { + if (peer->get_connection_terminated_time() != fc::time_point::min() && + peer->get_connection_terminated_time() < failed_terminate_threshold) { + wlog("Terminating connection with peer ${peer}, closing the connection didn't work", ("peer", peer->get_remote_endpoint())); + peers_to_terminate.push_back(peer); + } } + } // That's the end of the sorting step; now all peers that require further processing are now in one of the // lists peers_to_disconnect_gently, peers_to_disconnect_forcibly, peers_to_send_keep_alive, or peers_to_terminate @@ -1476,11 +1637,14 @@ namespace graphene { namespace net { namespace detail { // if we've decided to delete any peers, do it now; in its current implementation this doesn't yield, // and once we start yielding, we may find that we've moved that peer to another list (closed or active) // and that triggers assertions, maybe even errors - for (const peer_connection_ptr& peer : peers_to_terminate ) { - assert(_terminating_connections.find(peer) != _terminating_connections.end()); - _terminating_connections.erase(peer); - schedule_peer_for_deletion(peer); + fc::scoped_lock lock(_terminating_connections.get_mutex()); + for (const peer_connection_ptr& peer : peers_to_terminate ) + { + assert(_terminating_connections.find(peer) != _terminating_connections.end()); + _terminating_connections.erase(peer); + schedule_peer_for_deletion(peer); + } } peers_to_terminate.clear(); @@ -1499,6 +1663,7 @@ namespace graphene { namespace net { namespace detail { // disconnect reason, so it may yield) for( const peer_connection_ptr& peer : peers_to_disconnect_gently ) { + fc::scoped_lock lock(_active_connections.get_mutex()); fc::exception detailed_error( FC_LOG_MESSAGE(warn, "Disconnecting due to inactivity", ( "last_message_received_seconds_ago", (peer->get_last_message_received_time() - fc::time_point::now() ).count() / fc::seconds(1 ).count() ) ( "last_message_sent_seconds_ago", (peer->get_last_message_sent_time() - fc::time_point::now() ).count() / fc::seconds(1 ).count() ) @@ -1522,6 +1687,7 @@ namespace graphene { namespace net { namespace detail { { VERIFY_CORRECT_THREAD(); + fc::scoped_lock lock(_active_connections.get_mutex()); std::list original_active_peers(_active_connections.begin(), _active_connections.end()); for( const peer_connection_ptr& active_peer : original_active_peers ) { @@ -1674,13 +1840,15 @@ namespace graphene { namespace net { namespace detail { bool node_impl::is_accepting_new_connections() { VERIFY_CORRECT_THREAD(); - return !_p2p_network_connect_loop_done.canceled() && get_number_of_connections() <= _maximum_number_of_connections; + return !_node_is_shutting_down && (!_p2p_network_connect_loop_done.valid() || !_p2p_network_connect_loop_done.canceled()) && + get_number_of_connections() <= _maximum_number_of_connections; } bool node_impl::is_wanting_new_connections() { VERIFY_CORRECT_THREAD(); - return !_p2p_network_connect_loop_done.canceled() && get_number_of_connections() < _desired_number_of_connections; + return !_node_is_shutting_down && !_p2p_network_connect_loop_done.canceled() && + get_number_of_connections() < _desired_number_of_connections; } uint32_t node_impl::get_number_of_connections() @@ -1691,12 +1859,19 @@ namespace graphene { namespace net { namespace detail { peer_connection_ptr node_impl::get_peer_by_node_id(const node_id_t& node_id) { - for (const peer_connection_ptr& active_peer : _active_connections) - if (node_id == active_peer->node_id) - return active_peer; - for (const peer_connection_ptr& handshaking_peer : _handshaking_connections) - if (node_id == handshaking_peer->node_id) - return handshaking_peer; + { + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& active_peer : _active_connections) + if (node_id == active_peer->node_id) + return active_peer; + } + { + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for (const peer_connection_ptr& handshaking_peer : _handshaking_connections) + if (node_id == handshaking_peer->node_id) + return handshaking_peer; + } + return peer_connection_ptr(); } @@ -1708,18 +1883,25 @@ namespace graphene { namespace net { namespace detail { dlog("is_already_connected_to_id returning true because the peer is us"); return true; } - for (const peer_connection_ptr active_peer : _active_connections) - if (node_id == active_peer->node_id) - { - dlog("is_already_connected_to_id returning true because the peer is already in our active list"); - return true; + { + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr active_peer : _active_connections) { + if (node_id == active_peer->node_id) { + dlog("is_already_connected_to_id returning true because the peer is already in our active list"); + return true; + } } - for (const peer_connection_ptr handshaking_peer : _handshaking_connections) - if (node_id == handshaking_peer->node_id) - { - dlog("is_already_connected_to_id returning true because the peer is already in our handshaking list"); - return true; + } + { + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for (const peer_connection_ptr handshaking_peer : _handshaking_connections) { + if (node_id == handshaking_peer->node_id) { + dlog("is_already_connected_to_id returning true because the peer is already in our handshaking list"); + return true; + } } + } + return false; } @@ -1751,19 +1933,25 @@ namespace graphene { namespace net { namespace detail { ("max", _maximum_number_of_connections)); dlog(" my id is ${id}", ("id", _node_id)); - for (const peer_connection_ptr& active_connection : _active_connections) { - dlog(" active: ${endpoint} with ${id} [${direction}]", - ("endpoint", active_connection->get_remote_endpoint()) - ("id", active_connection->node_id) - ("direction", active_connection->direction)); + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& active_connection : _active_connections) + { + dlog(" active: ${endpoint} with ${id} [${direction}]", + ("endpoint", active_connection->get_remote_endpoint()) + ("id", active_connection->node_id) + ("direction", active_connection->direction)); + } } - for (const peer_connection_ptr& handshaking_connection : _handshaking_connections) { - dlog(" handshaking: ${endpoint} with ${id} [${direction}]", - ("endpoint", handshaking_connection->get_remote_endpoint()) - ("id", handshaking_connection->node_id) - ("direction", handshaking_connection->direction)); + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for (const peer_connection_ptr& handshaking_connection : _handshaking_connections) + { + dlog(" handshaking: ${endpoint} with ${id} [${direction}]", + ("endpoint", handshaking_connection->get_remote_endpoint()) + ("id", handshaking_connection->node_id) + ("direction", handshaking_connection->direction)); + } } } @@ -1870,6 +2058,7 @@ namespace graphene { namespace net { namespace detail { user_data["last_known_block_hash"] = fc::variant( head_block_id, 1 ); user_data["last_known_block_number"] = _delegate->get_block_number(head_block_id); user_data["last_known_block_time"] = _delegate->get_block_time(head_block_id); + user_data["last_known_hardfork_time"] = _delegate->get_last_known_hardfork_time().sec_since_epoch(); if (!_hard_fork_block_numbers.empty()) user_data["last_known_fork_block_number"] = _hard_fork_block_numbers.back(); @@ -1896,6 +2085,17 @@ namespace graphene { namespace net { namespace detail { originating_peer->node_id = user_data["node_id"].as(1); if (user_data.contains("last_known_fork_block_number")) originating_peer->last_known_fork_block_number = user_data["last_known_fork_block_number"].as(1); + if (user_data.contains("last_known_hardfork_time")){ + originating_peer->last_known_hardfork_time = fc::time_point_sec(user_data["last_known_hardfork_time"].as(1)); + }else{ + // this state is invalid when node which wants to connect doesn't provide + // last hardfork time. We are setting to 0 which will disconnect the node + // on hello message + originating_peer->last_known_hardfork_time = fc::time_point_sec(0); + if(DISABLE_WITNESS_HF_CHECK) { + originating_peer->last_known_hardfork_time = _delegate->get_last_known_hardfork_time(); + } + } } void node_impl::on_hello_message( peer_connection* originating_peer, const hello_message& hello_message_received ) @@ -1975,23 +2175,11 @@ namespace graphene { namespace net { namespace detail { disconnect_from_peer(originating_peer, "You are on a different chain from me"); return; } - if (originating_peer->last_known_fork_block_number != 0) - { - uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(originating_peer->last_known_fork_block_number); - if (next_fork_block_number != 0) - { - // we know about a fork they don't. See if we've already passed that block. If we have, don't let them - // connect because we won't be able to give them anything useful - uint32_t head_block_num = _delegate->get_block_number(_delegate->get_head_block_id()); - if (next_fork_block_number < head_block_num) - { -#ifdef ENABLE_DEBUG_ULOGS + + auto disconnect_peer = [&](const std::ostringstream& rejection_message) { + #ifdef ENABLE_DEBUG_ULOGS ulog("Rejecting connection from peer because their version is too old. Their version date: ${date}", ("date", originating_peer->graphene_git_revision_unix_timestamp)); -#endif - wlog("Received hello message from peer running a version of that can only understand blocks up to #${their_hard_fork}, but I'm at head block number #${my_block_number}", - ("their_hard_fork", next_fork_block_number)("my_block_number", head_block_num)); - std::ostringstream rejection_message; - rejection_message << "Your client is outdated -- you can only understand blocks up to #" << next_fork_block_number << ", but I'm already on block #" << head_block_num; + #endif connection_rejected_message connection_rejected(_user_agent_string, core_protocol_version, originating_peer->get_socket().remote_endpoint(), rejection_reason_code::unspecified, @@ -2003,10 +2191,42 @@ namespace graphene { namespace net { namespace detail { // allowing her to ask us for peers (any of our peers will be on the same chain as us, so there's no // benefit of sharing them) disconnect_from_peer(originating_peer, "Your client is too old, please upgrade"); + }; + + if (originating_peer->last_known_fork_block_number != 0) + { + uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(originating_peer->last_known_fork_block_number); + if (next_fork_block_number != 0) + { + // we know about a fork they don't. See if we've already passed that block. If we have, don't let them + // connect because we won't be able to give them anything useful + uint32_t head_block_num = _delegate->get_block_number(_delegate->get_head_block_id()); + if (next_fork_block_number < head_block_num) + { + wlog("Received hello message from peer running a version of that can only understand blocks up to #${their_hard_fork}, but I'm at head block number #${my_block_number}", + ("their_hard_fork", next_fork_block_number)("my_block_number", head_block_num)); + std::ostringstream rejection_message; + rejection_message << "Your client is outdated -- you can only understand blocks up to #" << next_fork_block_number << ", but I'm already on block #" << head_block_num; + disconnect_peer(rejection_message); return; } } } + + // we wan't to disconnect from the peer that didn't updated the software. With the last hardforks we could + // indetify if peer's are not compatible due the hardforks + if ( originating_peer->last_known_hardfork_time < _delegate->get_last_known_hardfork_time()) + { + if ((_delegate->get_block_time(_delegate->get_head_block_id()).sec_since_epoch() >= _delegate->get_last_known_hardfork_time().sec_since_epoch()) + || originating_peer->last_known_hardfork_time.sec_since_epoch() == 0) + { + std::ostringstream rejection_message; + rejection_message << "Your client is outdated -- you can only understand blocks up to #" << originating_peer->last_known_hardfork_time.to_iso_string() << ", but I'm already on block #" << _delegate->get_block_time(_delegate->get_head_block_id()).to_iso_string(); + disconnect_peer(rejection_message); + return; + } + } + if (already_connected_to_this_peer) { @@ -2178,6 +2398,7 @@ namespace graphene { namespace net { namespace detail { if (!_peer_advertising_disabled) { reply.addresses.reserve(_active_connections.size()); + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& active_peer : _active_connections) { fc::optional updated_peer_record = _potential_peer_db.lookup_entry_for_endpoint(*active_peer->get_remote_endpoint()); @@ -2363,11 +2584,14 @@ namespace graphene { namespace net { namespace detail { { VERIFY_CORRECT_THREAD(); uint32_t max_number_of_unfetched_items = 0; - for( const peer_connection_ptr& peer : _active_connections ) { - uint32_t this_peer_number_of_unfetched_items = (uint32_t)peer->ids_of_items_to_get.size() + peer->number_of_unfetched_item_ids; - max_number_of_unfetched_items = std::max(max_number_of_unfetched_items, - this_peer_number_of_unfetched_items); + fc::scoped_lock lock(_active_connections.get_mutex()); + for( const peer_connection_ptr& peer : _active_connections ) + { + uint32_t this_peer_number_of_unfetched_items = (uint32_t)peer->ids_of_items_to_get.size() + peer->number_of_unfetched_item_ids; + max_number_of_unfetched_items = std::max(max_number_of_unfetched_items, + this_peer_number_of_unfetched_items); + } } return max_number_of_unfetched_items; } @@ -2582,17 +2806,19 @@ namespace graphene { namespace net { namespace detail { originating_peer->ids_of_items_to_get.empty()) { bool is_first_item_for_other_peer = false; - for (const peer_connection_ptr& peer : _active_connections) - if (peer != originating_peer->shared_from_this() && - !peer->ids_of_items_to_get.empty() && - peer->ids_of_items_to_get.front() == blockchain_item_ids_inventory_message_received.item_hashes_available.front()) - { - dlog("The item ${newitem} is the first item for peer ${peer}", - ("newitem", blockchain_item_ids_inventory_message_received.item_hashes_available.front()) - ("peer", peer->get_remote_endpoint())); - is_first_item_for_other_peer = true; - break; + { + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { + if (peer != originating_peer->shared_from_this() && + !peer->ids_of_items_to_get.empty() && + peer->ids_of_items_to_get.front() == blockchain_item_ids_inventory_message_received.item_hashes_available.front()) { + dlog("The item ${newitem} is the first item for peer ${peer}", + ("newitem", blockchain_item_ids_inventory_message_received.item_hashes_available.front())("peer", peer->get_remote_endpoint())); + is_first_item_for_other_peer = true; + break; + } } + } dlog("is_first_item_for_other_peer: ${is_first}. item_hashes_received.size() = ${size}", ("is_first", is_first_item_for_other_peer)("size", item_hashes_received.size())); if (!is_first_item_for_other_peer) @@ -2882,15 +3108,18 @@ namespace graphene { namespace net { namespace detail { item_id advertised_item_id(item_ids_inventory_message_received.item_type, item_hash); bool we_advertised_this_item_to_a_peer = false; bool we_requested_this_item_from_a_peer = false; - for (const peer_connection_ptr peer : _active_connections) { - if (peer->inventory_advertised_to_peer.find(advertised_item_id) != peer->inventory_advertised_to_peer.end()) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr peer : _active_connections) { - we_advertised_this_item_to_a_peer = true; - break; + if (peer->inventory_advertised_to_peer.find(advertised_item_id) != peer->inventory_advertised_to_peer.end()) + { + we_advertised_this_item_to_a_peer = true; + break; + } + if (peer->items_requested_from_peer.find(advertised_item_id) != peer->items_requested_from_peer.end()) + we_requested_this_item_from_a_peer = true; } - if (peer->items_requested_from_peer.find(advertised_item_id) != peer->items_requested_from_peer.end()) - we_requested_this_item_from_a_peer = true; } // if we have already advertised it to a peer, we must have it, no need to do anything else @@ -3108,11 +3337,38 @@ namespace graphene { namespace net { namespace detail { --_total_number_of_unfetched_items; dlog("sync: client accpted the block, we now have only ${count} items left to fetch before we're in sync", ("count", _total_number_of_unfetched_items)); + + + auto disconnect_peer = [&](const std::ostringstream& disconnect_reason_stream, const peer_connection_ptr& peer, bool& disconnecting_this_peer) + { + peers_to_disconnect[peer] = std::make_pair(disconnect_reason_stream.str(), + fc::oexception(fc::exception(FC_LOG_MESSAGE(error, "You need to upgrade your client")))); + #ifdef ENABLE_DEBUG_ULOGS + ulog("Disconnecting from peer during sync because their version is too old. Their version date: ${date}", ("date", peer->graphene_git_revision_unix_timestamp)); + #endif + disconnecting_this_peer = true; + }; + bool is_fork_block = is_hard_fork_block(block_message_to_send.block.block_num()); + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& peer : _active_connections) { ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections bool disconnecting_this_peer = false; + + // if connected peer doesn't have the same version of witness which is fully indetified + // with last hardfork time received and block timestamp is grater than peers last known hardfork + // time disconnect that peer, since he will not be capable of handling already pushed block + if( peer->last_known_hardfork_time < _delegate->get_last_known_hardfork_time() ) + { + if( block_message_to_send.block.timestamp.sec_since_epoch() >= _delegate->get_last_known_hardfork_time().sec_since_epoch() ) + { + std::ostringstream disconnect_reason_stream; + disconnect_reason_stream << "You need to upgrade your client due to hard fork at block " << block_message_to_send.block.timestamp.to_iso_string(); + disconnect_peer(disconnect_reason_stream, peer, disconnecting_this_peer); + } + } + if (is_fork_block) { // we just pushed a hard fork block. Find out if this peer is running a client @@ -3125,16 +3381,11 @@ namespace graphene { namespace net { namespace detail { { std::ostringstream disconnect_reason_stream; disconnect_reason_stream << "You need to upgrade your client due to hard fork at block " << block_message_to_send.block.block_num(); - peers_to_disconnect[peer] = std::make_pair(disconnect_reason_stream.str(), - fc::oexception(fc::exception(FC_LOG_MESSAGE(error, "You need to upgrade your client due to hard fork at block ${block_number}", - ("block_number", block_message_to_send.block.block_num()))))); -#ifdef ENABLE_DEBUG_ULOGS - ulog("Disconnecting from peer during sync because their version is too old. Their version date: ${date}", ("date", peer->graphene_git_revision_unix_timestamp)); -#endif - disconnecting_this_peer = true; + disconnect_peer(disconnect_reason_stream, peer, disconnecting_this_peer); } } } + if (!disconnecting_this_peer && peer->ids_of_items_to_get.empty() && peer->ids_of_items_being_processed.empty()) { @@ -3182,6 +3433,7 @@ namespace graphene { namespace net { namespace detail { else { // invalid message received + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& peer : _active_connections) { ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections @@ -3284,15 +3536,18 @@ namespace graphene { namespace net { namespace detail { // find out if this block is the next block on the active chain or one of the forks bool potential_first_block = false; - for (const peer_connection_ptr& peer : _active_connections) { - ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections - if (!peer->ids_of_items_to_get.empty() && - peer->ids_of_items_to_get.front() == received_block_iter->block_id) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { - potential_first_block = true; - peer->ids_of_items_to_get.pop_front(); - peer->ids_of_items_being_processed.insert(received_block_iter->block_id); + ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections + if (!peer->ids_of_items_to_get.empty() && + peer->ids_of_items_to_get.front() == received_block_iter->block_id) + { + potential_first_block = true; + peer->ids_of_items_to_get.pop_front(); + peer->ids_of_items_being_processed.insert(received_block_iter->block_id); + } } } @@ -3320,6 +3575,7 @@ namespace graphene { namespace net { namespace detail { { dlog("Already received and accepted this block (presumably through normal inventory mechanism), treating it as accepted"); std::vector< peer_connection_ptr > peers_needing_next_batch; + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& peer : _active_connections) { auto items_being_processed_iter = peer->ids_of_items_being_processed.find(received_block_iter->block_id); @@ -3363,7 +3619,7 @@ namespace graphene { namespace net { namespace detail { dlog("leaving process_backlog_of_sync_blocks, ${count} processed", ("count", blocks_processed)); - if (!_suspend_fetching_sync_blocks) + if (!_suspend_fetching_sync_blocks && !_node_is_shutting_down) trigger_fetch_sync_items_loop(); } @@ -3397,8 +3653,16 @@ namespace graphene { namespace net { namespace detail { std::string disconnect_reason; fc::oexception disconnect_exception; fc::oexception restart_sync_exception; + bool rejecting_block_due_hf = false; + try { + if(_delegate->get_last_known_hardfork_time().sec_since_epoch() < originating_peer->last_known_hardfork_time.sec_since_epoch() + && block_message_to_process.block.timestamp.sec_since_epoch() >= originating_peer->last_known_hardfork_time.sec_since_epoch() ) + { + rejecting_block_due_hf = true; + } + // we can get into an intersting situation near the end of synchronization. We can be in // sync with one peer who is sending us the last block on the chain via a regular inventory // message, while at the same time still be synchronizing with a peer who is sending us the @@ -3407,7 +3671,7 @@ namespace graphene { namespace net { namespace detail { // message id, for the peer in the sync case we only known the block_id). fc::time_point message_validated_time; if (std::find(_most_recent_blocks_accepted.begin(), _most_recent_blocks_accepted.end(), - block_message_to_process.block_id) == _most_recent_blocks_accepted.end()) + block_message_to_process.block_id) == _most_recent_blocks_accepted.end() && !rejecting_block_due_hf) { std::vector contained_transaction_message_ids; _delegate->handle_block(block_message_to_process, false, contained_transaction_message_ids); @@ -3436,62 +3700,90 @@ namespace graphene { namespace net { namespace detail { if (new_transaction_discovered) trigger_advertise_inventory_loop(); } - else - dlog( "Already received and accepted this block (presumably through sync mechanism), treating it as accepted" ); + else { + dlog( "Already received and accepted this block (presumably through sync mechanism), treating it as accepted or non compatible node witness"); + } dlog( "client validated the block, advertising it to other peers" ); item_id block_message_item_id(core_message_type_enum::block_message_type, message_hash); uint32_t block_number = block_message_to_process.block.block_num(); fc::time_point_sec block_time = block_message_to_process.block.timestamp; + bool disconnect_this_peer = false; - for (const peer_connection_ptr& peer : _active_connections) { - ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections - - auto iter = peer->inventory_peer_advertised_to_us.find(block_message_item_id); - if (iter != peer->inventory_peer_advertised_to_us.end()) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { - // this peer offered us the item. It will eventually expire from the peer's - // inventory_peer_advertised_to_us list after some time has passed (currently 2 minutes). - // For now, it will remain there, which will prevent us from offering the peer this - // block back when we rebroadcast the block below - peer->last_block_delegate_has_seen = block_message_to_process.block_id; - peer->last_block_time_delegate_has_seen = block_time; + ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections + + auto iter = peer->inventory_peer_advertised_to_us.find(block_message_item_id); + if (iter != peer->inventory_peer_advertised_to_us.end()) + { + // this peer offered us the item. It will eventually expire from the peer's + // inventory_peer_advertised_to_us list after some time has passed (currently 2 minutes). + // For now, it will remain there, which will prevent us from offering the peer this + // block back when we rebroadcast the block below + peer->last_block_delegate_has_seen = block_message_to_process.block_id; + peer->last_block_time_delegate_has_seen = block_time; + } + peer->clear_old_inventory(); } - peer->clear_old_inventory(); } + message_propagation_data propagation_data{message_receive_time, message_validated_time, originating_peer->node_id}; broadcast( block_message_to_process, propagation_data ); _message_cache.block_accepted(); - if (is_hard_fork_block(block_number)) { - // we just pushed a hard fork block. Find out if any of our peers are running clients - // that will be unable to process future blocks + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& peer : _active_connections) { - if (peer->last_known_fork_block_number != 0) + if (is_hard_fork_block(block_number) ) { - uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(peer->last_known_fork_block_number); - if (next_fork_block_number != 0 && - next_fork_block_number <= block_number) + if (peer->last_known_fork_block_number != 0) { - peers_to_disconnect.insert(peer); -#ifdef ENABLE_DEBUG_ULOGS - ulog("Disconnecting from peer because their version is too old. Their version date: ${date}", ("date", peer->graphene_git_revision_unix_timestamp)); -#endif + uint32_t next_fork_block_number = get_next_known_hard_fork_block_number(peer->last_known_fork_block_number); + if (next_fork_block_number != 0 && + next_fork_block_number <= block_number) + { + disconnect_this_peer = true; + } } } + + if(peer->last_known_hardfork_time < _delegate->get_last_known_hardfork_time()) + { + if(block_message_to_process.block.timestamp.sec_since_epoch() >= _delegate->get_last_known_hardfork_time().sec_since_epoch()) + { + disconnect_this_peer = true; + } + } + + if( disconnect_this_peer ) + { + peers_to_disconnect.insert(peer); +#ifdef ENABLE_DEBUG_ULOGS + ulog("Disconnecting from peer because their version is too old. Their version date: ${date}", ("date", peer->graphene_git_revision_unix_timestamp)); +#endif + } } - if (!peers_to_disconnect.empty()) - { - std::ostringstream disconnect_reason_stream; - disconnect_reason_stream << "You need to upgrade your client due to hard fork at block " << block_number; - disconnect_reason = disconnect_reason_stream.str(); - disconnect_exception = fc::exception(FC_LOG_MESSAGE(error, "You need to upgrade your client due to hard fork at block ${block_number}", + } + + if(rejecting_block_due_hf) + { + // disconnect originated peer since we rejected the block from him due + // the not anymore compatible witness nodes + peers_to_disconnect.insert(originating_peer->shared_from_this()); + } + + if (!peers_to_disconnect.empty()) + { + std::ostringstream disconnect_reason_stream; + disconnect_reason_stream << "You need to upgrade your client due to hard fork at block " << block_number; + disconnect_reason = disconnect_reason_stream.str(); + disconnect_exception = fc::exception(FC_LOG_MESSAGE(error, "You need to upgrade your client due to hard fork at block ${block_number}", ("block_number", block_number))); - } } } catch (const fc::canceled_exception&) @@ -3513,9 +3805,11 @@ namespace graphene { namespace net { namespace detail { disconnect_reason = "You offered me a block that I have deemed to be invalid"; peers_to_disconnect.insert( originating_peer->shared_from_this() ); - for (const peer_connection_ptr& peer : _active_connections) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { if (!peer->ids_of_items_to_get.empty() && peer->ids_of_items_to_get.front() == block_message_to_process.block_id) peers_to_disconnect.insert(peer); + } } if (restart_sync_exception) @@ -3636,25 +3930,29 @@ namespace graphene { namespace net { namespace detail { void node_impl::forward_firewall_check_to_next_available_peer(firewall_check_state_data* firewall_check_state) { - for (const peer_connection_ptr& peer : _active_connections) { - if (firewall_check_state->expected_node_id != peer->node_id && // it's not the node who is asking us to test - !peer->firewall_check_state && // the peer isn't already performing a check for another node - firewall_check_state->nodes_already_tested.find(peer->node_id) == firewall_check_state->nodes_already_tested.end() && - peer->core_protocol_version >= 106) + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) { - wlog("forwarding firewall check for node ${to_check} to peer ${checker}", - ("to_check", firewall_check_state->endpoint_to_test) - ("checker", peer->get_remote_endpoint())); - firewall_check_state->nodes_already_tested.insert(peer->node_id); - peer->firewall_check_state = firewall_check_state; - check_firewall_message check_request; - check_request.endpoint_to_check = firewall_check_state->endpoint_to_test; - check_request.node_id = firewall_check_state->expected_node_id; - peer->send_message(check_request); - return; + if (firewall_check_state->expected_node_id != peer->node_id && // it's not the node who is asking us to test + !peer->firewall_check_state && // the peer isn't already performing a check for another node + firewall_check_state->nodes_already_tested.find(peer->node_id) == firewall_check_state->nodes_already_tested.end() && + peer->core_protocol_version >= 106) + { + wlog("forwarding firewall check for node ${to_check} to peer ${checker}", + ("to_check", firewall_check_state->endpoint_to_test) + ("checker", peer->get_remote_endpoint())); + firewall_check_state->nodes_already_tested.insert(peer->node_id); + peer->firewall_check_state = firewall_check_state; + check_firewall_message check_request; + check_request.endpoint_to_check = firewall_check_state->endpoint_to_test; + check_request.node_id = firewall_check_state->expected_node_id; + peer->send_message(check_request); + return; + } } } + wlog("Unable to forward firewall check for node ${to_check} to any other peers, returning 'unable'", ("to_check", firewall_check_state->endpoint_to_test)); @@ -3827,41 +4125,45 @@ namespace graphene { namespace net { namespace detail { } fc::time_point now = fc::time_point::now(); - for (const peer_connection_ptr& peer : _active_connections) { - ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr& peer : _active_connections) + { + ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections - current_connection_data data_for_this_peer; - data_for_this_peer.connection_duration = now.sec_since_epoch() - peer->connection_initiation_time.sec_since_epoch(); - if (peer->get_remote_endpoint()) // should always be set for anyone we're actively connected to - data_for_this_peer.remote_endpoint = *peer->get_remote_endpoint(); - data_for_this_peer.clock_offset = peer->clock_offset; - data_for_this_peer.round_trip_delay = peer->round_trip_delay; - data_for_this_peer.node_id = peer->node_id; - data_for_this_peer.connection_direction = peer->direction; - data_for_this_peer.firewalled = peer->is_firewalled; - fc::mutable_variant_object user_data; - if (peer->graphene_git_revision_sha) - user_data["graphene_git_revision_sha"] = *peer->graphene_git_revision_sha; - if (peer->graphene_git_revision_unix_timestamp) - user_data["graphene_git_revision_unix_timestamp"] = *peer->graphene_git_revision_unix_timestamp; - if (peer->fc_git_revision_sha) - user_data["fc_git_revision_sha"] = *peer->fc_git_revision_sha; - if (peer->fc_git_revision_unix_timestamp) - user_data["fc_git_revision_unix_timestamp"] = *peer->fc_git_revision_unix_timestamp; - if (peer->platform) - user_data["platform"] = *peer->platform; - if (peer->bitness) - user_data["bitness"] = *peer->bitness; - user_data["user_agent"] = peer->user_agent; + current_connection_data data_for_this_peer; + data_for_this_peer.connection_duration = now.sec_since_epoch() - peer->connection_initiation_time.sec_since_epoch(); + if (peer->get_remote_endpoint()) // should always be set for anyone we're actively connected to + data_for_this_peer.remote_endpoint = *peer->get_remote_endpoint(); + data_for_this_peer.clock_offset = peer->clock_offset; + data_for_this_peer.round_trip_delay = peer->round_trip_delay; + data_for_this_peer.node_id = peer->node_id; + data_for_this_peer.connection_direction = peer->direction; + data_for_this_peer.firewalled = peer->is_firewalled; + fc::mutable_variant_object user_data; + if (peer->graphene_git_revision_sha) + user_data["graphene_git_revision_sha"] = *peer->graphene_git_revision_sha; + if (peer->graphene_git_revision_unix_timestamp) + user_data["graphene_git_revision_unix_timestamp"] = *peer->graphene_git_revision_unix_timestamp; + if (peer->fc_git_revision_sha) + user_data["fc_git_revision_sha"] = *peer->fc_git_revision_sha; + if (peer->fc_git_revision_unix_timestamp) + user_data["fc_git_revision_unix_timestamp"] = *peer->fc_git_revision_unix_timestamp; + if (peer->platform) + user_data["platform"] = *peer->platform; + if (peer->bitness) + user_data["bitness"] = *peer->bitness; + user_data["user_agent"] = peer->user_agent; - user_data["last_known_block_hash"] = fc::variant( peer->last_block_delegate_has_seen, 1 ); - user_data["last_known_block_number"] = _delegate->get_block_number(peer->last_block_delegate_has_seen); - user_data["last_known_block_time"] = peer->last_block_time_delegate_has_seen; + user_data["last_known_block_hash"] = fc::variant( peer->last_block_delegate_has_seen, 1 ); + user_data["last_known_block_number"] = _delegate->get_block_number(peer->last_block_delegate_has_seen); + user_data["last_known_block_time"] = peer->last_block_time_delegate_has_seen; - data_for_this_peer.user_data = user_data; - reply.current_connections.emplace_back(data_for_this_peer); + data_for_this_peer.user_data = user_data; + reply.current_connections.emplace_back(data_for_this_peer); + } } + originating_peer->send_message(reply); } @@ -3946,6 +4248,7 @@ namespace graphene { namespace net { namespace detail { void node_impl::start_synchronizing() { + fc::scoped_lock lock(_active_connections.get_mutex()); for( const peer_connection_ptr& peer : _active_connections ) start_synchronizing_with_peer( peer ); } @@ -3967,7 +4270,7 @@ namespace graphene { namespace net { namespace detail { { VERIFY_CORRECT_THREAD(); - _node_is_shutting_down = true; + _node_is_shutting_down.store(true); try { @@ -4152,9 +4455,19 @@ namespace graphene { namespace net { namespace detail { // the read loop before it gets an EOF). // operate off copies of the lists in case they change during iteration std::list all_peers; - boost::push_back(all_peers, _active_connections); - boost::push_back(all_peers, _handshaking_connections); - boost::push_back(all_peers, _closing_connections); + auto p_back = [&all_peers](const peer_connection_ptr& conn) { all_peers.push_back(conn); }; + { + fc::scoped_lock lock(_active_connections.get_mutex()); + std::for_each(_active_connections.begin(), _active_connections.end(), p_back); + } + { + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + std::for_each(_handshaking_connections.begin(), _handshaking_connections.end(), p_back); + } + { + fc::scoped_lock lock(_closing_connections.get_mutex()); + std::for_each(_closing_connections.begin(), _closing_connections.end(), p_back); + } for (const peer_connection_ptr& peer : all_peers) { @@ -4420,9 +4733,7 @@ namespace graphene { namespace net { namespace detail { // whether the peer is firewalled, we want to disconnect now. _handshaking_connections.erase(new_peer); _terminating_connections.erase(new_peer); - assert(_active_connections.find(new_peer) == _active_connections.end()); _active_connections.erase(new_peer); - assert(_closing_connections.find(new_peer) == _closing_connections.end()); _closing_connections.erase(new_peer); display_current_connections(); @@ -4666,7 +4977,69 @@ namespace graphene { namespace net { namespace detail { _potential_peer_db.update_entry(updated_peer_record); trigger_p2p_network_connect_loop(); } + void node_impl::add_seed_node(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + _seed_nodes.insert( endpoint_string ); + resolve_seed_node_and_add( endpoint_string ); + } + void node_impl::resolve_seed_node_and_add(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + std::vector endpoints; + ilog("Resolving seed node ${endpoint}", ("endpoint", endpoint_string)); + try + { + endpoints = graphene::net::node::resolve_string_to_ip_endpoints(endpoint_string); + } + catch(...) + { + wlog( "Unable to resolve endpoint during attempt to add seed node ${ep}", ("ep", endpoint_string) ); + } + for (const fc::ip::endpoint& endpoint : endpoints) + { + ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); + add_node(endpoint); + } + } + void node_impl::update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + try + { + dlog("Starting an iteration of update_seed_nodes loop."); + for( const std::string& endpoint_string : _seed_nodes ) + { + resolve_seed_node_and_add( endpoint_string ); + } + dlog("Done an iteration of update_seed_nodes loop."); + } + catch (const fc::canceled_exception&) + { + ilog( "update_seed_nodes_task canceled" ); + throw; + } + FC_CAPTURE_AND_LOG( (_seed_nodes) ) + + schedule_next_update_seed_nodes_task(); + } + + void node_impl::schedule_next_update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + + if( _node_is_shutting_down ) + return; + + if( _update_seed_nodes_loop_done.valid() && _update_seed_nodes_loop_done.canceled() ) + return; + + _update_seed_nodes_loop_done = fc::schedule( [this]() { update_seed_nodes_task(); }, + fc::time_point::now() + fc::hours(3), + "update_seed_nodes_loop" ); + } + void node_impl::initiate_connect_to(const peer_connection_ptr& new_peer) { new_peer->get_socket().open(); @@ -4704,18 +5077,25 @@ namespace graphene { namespace net { namespace detail { peer_connection_ptr node_impl::get_connection_to_endpoint( const fc::ip::endpoint& remote_endpoint ) { VERIFY_CORRECT_THREAD(); - for( const peer_connection_ptr& active_peer : _active_connections ) { - fc::optional endpoint_for_this_peer( active_peer->get_remote_endpoint() ); - if( endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint ) - return active_peer; + fc::scoped_lock lock(_active_connections.get_mutex()); + for( const peer_connection_ptr& active_peer : _active_connections ) + { + fc::optional endpoint_for_this_peer( active_peer->get_remote_endpoint() ); + if( endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint ) + return active_peer; + } } - for( const peer_connection_ptr& handshaking_peer : _handshaking_connections ) { - fc::optional endpoint_for_this_peer( handshaking_peer->get_remote_endpoint() ); - if( endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint ) - return handshaking_peer; + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for( const peer_connection_ptr& handshaking_peer : _handshaking_connections ) + { + fc::optional endpoint_for_this_peer( handshaking_peer->get_remote_endpoint() ); + if( endpoint_for_this_peer && *endpoint_for_this_peer == remote_endpoint ) + return handshaking_peer; + } } + return peer_connection_ptr(); } @@ -4759,21 +5139,27 @@ namespace graphene { namespace net { namespace detail { ilog( " number of peers: ${active} active, ${handshaking}, ${closing} closing. attempting to maintain ${desired} - ${maximum} peers", ( "active", _active_connections.size() )("handshaking", _handshaking_connections.size() )("closing",_closing_connections.size() ) ( "desired", _desired_number_of_connections )("maximum", _maximum_number_of_connections ) ); - for( const peer_connection_ptr& peer : _active_connections ) { - ilog( " active peer ${endpoint} peer_is_in_sync_with_us:${in_sync_with_us} we_are_in_sync_with_peer:${in_sync_with_them}", - ( "endpoint", peer->get_remote_endpoint() ) - ( "in_sync_with_us", !peer->peer_needs_sync_items_from_us )("in_sync_with_them", !peer->we_need_sync_items_from_peer ) ); - if( peer->we_need_sync_items_from_peer ) - ilog( " above peer has ${count} sync items we might need", ("count", peer->ids_of_items_to_get.size() ) ); - if (peer->inhibit_fetching_sync_blocks) - ilog( " we are not fetching sync blocks from the above peer (inhibit_fetching_sync_blocks == true)" ); + fc::scoped_lock lock(_active_connections.get_mutex()); + for( const peer_connection_ptr& peer : _active_connections ) + { + ilog( " active peer ${endpoint} peer_is_in_sync_with_us:${in_sync_with_us} we_are_in_sync_with_peer:${in_sync_with_them}", + ( "endpoint", peer->get_remote_endpoint() ) + ( "in_sync_with_us", !peer->peer_needs_sync_items_from_us )("in_sync_with_them", !peer->we_need_sync_items_from_peer ) ); + if( peer->we_need_sync_items_from_peer ) + ilog( " above peer has ${count} sync items we might need", ("count", peer->ids_of_items_to_get.size() ) ); + if (peer->inhibit_fetching_sync_blocks) + ilog( " we are not fetching sync blocks from the above peer (inhibit_fetching_sync_blocks == true)" ); + } } - for( const peer_connection_ptr& peer : _handshaking_connections ) { - ilog( " handshaking peer ${endpoint} in state ours(${our_state}) theirs(${their_state})", - ( "endpoint", peer->get_remote_endpoint() )("our_state", peer->our_state )("their_state", peer->their_state ) ); + fc::scoped_lock lock(_handshaking_connections.get_mutex()); + for( const peer_connection_ptr& peer : _handshaking_connections ) + { + ilog( " handshaking peer ${endpoint} in state ours(${our_state}) theirs(${their_state})", + ( "endpoint", peer->get_remote_endpoint() )("our_state", peer->our_state )("their_state", peer->their_state ) ); + } } ilog( "--------- MEMORY USAGE ------------" ); @@ -4783,6 +5169,7 @@ namespace graphene { namespace net { namespace detail { ilog( "node._items_to_fetch size: ${size}", ("size", _items_to_fetch.size() ) ); ilog( "node._new_inventory size: ${size}", ("size", _new_inventory.size() ) ); ilog( "node._message_cache size: ${size}", ("size", _message_cache.size() ) ); + fc::scoped_lock lock(_active_connections.get_mutex()); for( const peer_connection_ptr& peer : _active_connections ) { ilog( " peer ${endpoint}", ("endpoint", peer->get_remote_endpoint() ) ); @@ -4880,6 +5267,7 @@ namespace graphene { namespace net { namespace detail { { VERIFY_CORRECT_THREAD(); std::vector statuses; + fc::scoped_lock lock(_active_connections.get_mutex()); for (const peer_connection_ptr& peer : _active_connections) { ASSERT_TASK_NOT_PREEMPTED(); // don't yield while iterating over _active_connections @@ -5070,10 +5458,13 @@ namespace graphene { namespace net { namespace detail { _allowed_peers.clear(); _allowed_peers.insert(allowed_peers.begin(), allowed_peers.end()); std::list peers_to_disconnect; - if (!_allowed_peers.empty()) - for (const peer_connection_ptr& peer : _active_connections) + if (!_allowed_peers.empty()) { + fc::scoped_lock lock(_active_connections.get_mutex()); + for (const peer_connection_ptr &peer : _active_connections) { if (_allowed_peers.find(peer->node_id) == _allowed_peers.end()) peers_to_disconnect.push_back(peer); + } + } for (const peer_connection_ptr& peer : peers_to_disconnect) disconnect_from_peer(peer.get(), "My allowed_peers list has changed, and you're no longer allowed. Bye."); #endif // ENABLE_P2P_DEBUGGING_API @@ -5205,6 +5596,11 @@ namespace graphene { namespace net { namespace detail { INVOKE_IN_IMPL(add_node, ep); } + void node::add_seed_node(const std::string& in) + { + INVOKE_IN_IMPL(add_seed_node, in); + } + void node::connect_to_endpoint( const fc::ip::endpoint& remote_endpoint ) { INVOKE_IN_IMPL(connect_to_endpoint, remote_endpoint); @@ -5550,6 +5946,14 @@ namespace graphene { namespace net { namespace detail { return _node_delegate->get_block_number(block_id); } + fc::time_point_sec statistics_gathering_node_delegate_wrapper::get_last_known_hardfork_time() + { + // this function doesn't need to block, + ASSERT_TASK_NOT_PREEMPTED(); + return _node_delegate->get_last_known_hardfork_time(); + } + + fc::time_point_sec statistics_gathering_node_delegate_wrapper::get_block_time(const item_hash_t& block_id) { INVOKE_AND_COLLECT_STATISTICS(get_block_time, block_id); @@ -5578,5 +5982,45 @@ namespace graphene { namespace net { namespace detail { #undef INVOKE_AND_COLLECT_STATISTICS } // end namespace detail + std::vector node::resolve_string_to_ip_endpoints(const std::string& in) + { + try + { + std::string::size_type colon_pos = in.find(':'); + if (colon_pos == std::string::npos) + FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", + ("endpoint_string", in)); + std::string port_string = in.substr(colon_pos + 1); + try + { + uint16_t port = boost::lexical_cast(port_string); + + std::string hostname = in.substr(0, colon_pos); + std::vector endpoints = fc::resolve(hostname, port); + if (endpoints.empty()) + FC_THROW_EXCEPTION( fc::unknown_host_exception, + "The host name can not be resolved: ${hostname}", + ("hostname", hostname) ); + return endpoints; + } + catch (const boost::bad_lexical_cast&) + { + FC_THROW("Bad port: ${port}", ("port", port_string)); + } + } + FC_CAPTURE_AND_RETHROW((in)) + } + void node::add_seed_nodes(std::vector seeds) + { + for(const std::string& endpoint_string : seeds ) + { + try { + add_seed_node(endpoint_string); + } catch( const fc::exception& e ) { + wlog( "caught exception ${e} while adding seed node ${endpoint}", + ("e", e.to_detail_string())("endpoint", endpoint_string) ); + } + } + } } } // end namespace graphene::net diff --git a/libraries/net/peer_database.cpp b/libraries/net/peer_database.cpp index 76ae9c8c..20bc5da9 100644 --- a/libraries/net/peer_database.cpp +++ b/libraries/net/peer_database.cpp @@ -50,7 +50,8 @@ namespace graphene { namespace net { indexed_by, member >, + &potential_peer_record::last_seen_time>, + std::greater >, hashed_unique, member >& hist = db.get_applied_operations(); bool is_first = true; auto skip_oho_id = [&is_first,&db,this]() { + const std::lock_guard undo_db_lock{db._undo_db_mutex}; if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo { db.remove( db.create( []( operation_history_object& obj) {} ) ); diff --git a/libraries/plugins/debug_witness/CMakeLists.txt b/libraries/plugins/debug_witness/CMakeLists.txt index ac7d7fc9..b9582548 100644 --- a/libraries/plugins/debug_witness/CMakeLists.txt +++ b/libraries/plugins/debug_witness/CMakeLists.txt @@ -16,3 +16,4 @@ install( TARGETS LIBRARY DESTINATION lib ARCHIVE DESTINATION lib ) +INSTALL( FILES ${HEADERS} DESTINATION "include/graphene/debug_witness" ) diff --git a/libraries/plugins/debug_witness/debug_api.cpp b/libraries/plugins/debug_witness/debug_api.cpp index 43ffd6cd..7fd8792a 100644 --- a/libraries/plugins/debug_witness/debug_api.cpp +++ b/libraries/plugins/debug_witness/debug_api.cpp @@ -34,7 +34,9 @@ class debug_api_impl }; debug_api_impl::debug_api_impl( graphene::app::application& _app ) : app( _app ) -{} +{ + // Nothing else to do +} void debug_api_impl::debug_push_blocks( const std::string& src_filename, uint32_t count ) diff --git a/libraries/plugins/debug_witness/debug_witness.cpp b/libraries/plugins/debug_witness/debug_witness.cpp index 7268006d..1a953316 100644 --- a/libraries/plugins/debug_witness/debug_witness.cpp +++ b/libraries/plugins/debug_witness/debug_witness.cpp @@ -38,7 +38,10 @@ using std::vector; namespace bpo = boost::program_options; -debug_witness_plugin::~debug_witness_plugin() {} +debug_witness_plugin::~debug_witness_plugin() +{ + cleanup(); +} void debug_witness_plugin::plugin_set_program_options( boost::program_options::options_description& command_line_options, @@ -62,7 +65,7 @@ void debug_witness_plugin::plugin_initialize(const boost::program_options::varia ilog("debug_witness plugin: plugin_initialize() begin"); _options = &options; - if( options.count("debug-private-key") ) + if( options.count("debug-private-key") > 0 ) { const std::vector key_id_to_wif_pair_strings = options["debug-private-key"].as>(); for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings) @@ -100,7 +103,6 @@ void debug_witness_plugin::plugin_startup() _changed_objects_conn = db.changed_objects.connect([this](const std::vector& ids, const fc::flat_set& impacted_accounts){ on_changed_objects(ids, impacted_accounts); }); _removed_objects_conn = db.removed_objects.connect([this](const std::vector& ids, const std::vector& objs, const fc::flat_set& impacted_accounts){ on_removed_objects(ids, objs, impacted_accounts); }); - return; } void debug_witness_plugin::on_changed_objects( const std::vector& ids, const fc::flat_set& impacted_accounts ) @@ -155,11 +157,15 @@ void debug_witness_plugin::flush_json_object_stream() } void debug_witness_plugin::plugin_shutdown() +{ + cleanup(); +} + +void debug_witness_plugin::cleanup() { if( _json_object_stream ) { _json_object_stream->close(); _json_object_stream.reset(); } - return; } diff --git a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp index 907d26ae..8b7cfd08 100644 --- a/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp +++ b/libraries/plugins/debug_witness/include/graphene/debug_witness/debug_witness.hpp @@ -34,23 +34,25 @@ namespace graphene { namespace debug_witness_plugin { class debug_witness_plugin : public graphene::app::plugin { public: - ~debug_witness_plugin(); + using graphene::app::plugin::plugin; + ~debug_witness_plugin() override; std::string plugin_name()const override; - virtual void plugin_set_program_options( + void plugin_set_program_options( boost::program_options::options_description &command_line_options, boost::program_options::options_description &config_file_options ) override; - virtual void plugin_initialize( const boost::program_options::variables_map& options ) override; - virtual void plugin_startup() override; - virtual void plugin_shutdown() override; + void plugin_initialize( const boost::program_options::variables_map& options ) override; + void plugin_startup() override; + void plugin_shutdown() override; void set_json_object_stream( const std::string& filename ); void flush_json_object_stream(); private: + void cleanup(); void on_changed_objects( const std::vector& ids, const fc::flat_set& impacted_accounts ); void on_removed_objects( const std::vector& ids, const std::vector objs, const fc::flat_set& impacted_accounts ); @@ -58,7 +60,7 @@ private: boost::program_options::variables_map _options; - std::map _private_keys; + std::map _private_keys; std::shared_ptr< std::ofstream > _json_object_stream; boost::signals2::scoped_connection _applied_block_conn; diff --git a/libraries/plugins/delayed_node/delayed_node_plugin.cpp b/libraries/plugins/delayed_node/delayed_node_plugin.cpp index 71de7db5..99b02378 100644 --- a/libraries/plugins/delayed_node/delayed_node_plugin.cpp +++ b/libraries/plugins/delayed_node/delayed_node_plugin.cpp @@ -63,8 +63,24 @@ void delayed_node_plugin::plugin_set_program_options(bpo::options_description& c void delayed_node_plugin::connect() { - my->client_connection = std::make_shared(my->client.connect(my->remote_endpoint), GRAPHENE_MAX_NESTED_OBJECTS); + fc::http::websocket_connection_ptr con; + try + { + con = my->client.connect(my->remote_endpoint); + } + catch( const fc::exception& e ) + { + wlog("Error while connecting: ${e}", ("e", e.to_detail_string())); + connection_failed(); + return; + } + my->client_connection = std::make_shared( + con, GRAPHENE_NET_MAX_NESTED_OBJECTS ); my->database_api = my->client_connection->get_remote_api(0); + my->database_api->set_block_applied_callback([this]( const fc::variant& block_id ) + { + fc::from_variant( block_id, my->last_received_remote_head, GRAPHENE_MAX_NESTED_OBJECTS ); + } ); my->client_connection_closed = my->client_connection->closed.connect([this] { connection_failed(); }); @@ -73,7 +89,9 @@ void delayed_node_plugin::connect() void delayed_node_plugin::plugin_initialize(const boost::program_options::variables_map& options) { FC_ASSERT(options.count("trusted-node") > 0); + ilog("delayed_node_plugin: plugin_initialize() begin"); my->remote_endpoint = "ws://" + options.at("trusted-node").as(); + ilog("delayed_node_plugin: plugin_initialize() end"); } void delayed_node_plugin::sync_with_trusted_node() @@ -100,8 +118,11 @@ void delayed_node_plugin::sync_with_trusted_node() while( remote_dpo.last_irreversible_block_num > db.head_block_num() ) { fc::optional block = my->database_api->get_block( db.head_block_num()+1 ); + // TODO: during sync, decouple requesting blocks from preprocessing + applying them FC_ASSERT(block, "Trusted node claims it has blocks it doesn't actually have."); ilog("Pushing block #${n}", ("n", block->block_num())); + // timur: failed to merge from bitshares, API n/a in peerplays + // db.precompute_parallel( *block, graphene::chain::database::skip_nothing ).wait(); db.push_block(*block); synced_blocks++; } @@ -136,24 +157,12 @@ void delayed_node_plugin::plugin_startup() mainloop(); }); - try - { - connect(); - my->database_api->set_block_applied_callback([this]( const fc::variant& block_id ) - { - fc::from_variant( block_id, my->last_received_remote_head, GRAPHENE_MAX_NESTED_OBJECTS ); - } ); - return; - } - catch (const fc::exception& e) - { - elog("Error during connection: ${e}", ("e", e.to_detail_string())); - } - fc::async([this]{connection_failed();}); + connect(); } void delayed_node_plugin::connection_failed() { + my->last_received_remote_head = my->last_processed_remote_head; elog("Connection to trusted node failed; retrying in 5 seconds..."); fc::schedule([this]{connect();}, fc::time_point::now() + fc::seconds(5)); } diff --git a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp index e306054a..fae54da2 100644 --- a/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp +++ b/libraries/plugins/elasticsearch/elasticsearch_plugin.cpp @@ -127,6 +127,7 @@ bool elasticsearch_plugin_impl::update_account_histories( const signed_block& b const vector >& hist = db.get_applied_operations(); bool is_first = true; auto skip_oho_id = [&is_first,&db,this]() { + const std::lock_guard undo_db_lock{db._undo_db_mutex}; if( is_first && db._undo_db.enabled() ) // this ensures that the current id is rolled back on undo { db.remove( db.create( []( operation_history_object& obj) {} ) ); diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt old mode 100755 new mode 100644 index 9ea2ce34..21fac167 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -3,9 +3,10 @@ file(GLOB_RECURSE HEADERS "include/graphene/peerplays_sidechain/*.hpp") add_library( peerplays_sidechain peerplays_sidechain_plugin.cpp sidechain_api.cpp - sidechain_net_manager.cpp + sidechain_net_handler_factory.cpp sidechain_net_handler.cpp sidechain_net_handler_bitcoin.cpp + sidechain_net_handler_ethereum.cpp sidechain_net_handler_hive.cpp sidechain_net_handler_peerplays.cpp bitcoin/bech32.cpp @@ -15,8 +16,15 @@ add_library( peerplays_sidechain bitcoin/segwit_addr.cpp bitcoin/utils.cpp bitcoin/sign_bitcoin_transaction.cpp + bitcoin/libbitcoin_client.cpp + bitcoin/estimate_fee_external.cpp common/rpc_client.cpp common/utils.cpp + ethereum/encoders.cpp + ethereum/decoders.cpp + ethereum/transaction.cpp + ethereum/types.cpp + ethereum/utils.cpp hive/asset.cpp hive/operations.cpp hive/transaction.cpp @@ -36,7 +44,7 @@ endif() unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS) unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS CACHE) -target_link_libraries( peerplays_sidechain PRIVATE curl graphene_plugin zmq ) +target_link_libraries( peerplays_sidechain PRIVATE curl graphene_plugin sha3 zmq bitcoin-system bitcoin-protocol bitcoin-client bitcoin-explorer ) target_include_directories( peerplays_sidechain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp index 69b28ee1..4eb9803d 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin/bitcoin_address.cpp @@ -242,12 +242,12 @@ bytes btc_multisig_segwit_address::get_address_bytes(const bytes &script_hash) { } btc_weighted_multisig_address::btc_weighted_multisig_address(const std::vector> &keys_data, - network ntype) { + network ntype, payment_type type) { network_type = ntype; + this->type = type; create_redeem_script(keys_data); create_witness_script(); create_segwit_address(); - type = payment_type::P2WSH; } void btc_weighted_multisig_address::create_redeem_script(const std::vector> &keys_data) { @@ -278,26 +278,43 @@ void btc_weighted_multisig_address::create_witness_script() { script_builder builder; builder << op::_0; builder << fc::sha256::hash(redeem_script_.data(), redeem_script_.size()); - witness_script_ = builder; } void btc_weighted_multisig_address::create_segwit_address() { std::string hrp; + address_types byte_version; switch (network_type) { case (network::mainnet): hrp = "bc"; + byte_version = address_types::MAINNET_SCRIPT; break; case (network::testnet): hrp = "tb"; + byte_version = address_types::TESTNET_SCRIPT; break; case (network::regtest): hrp = "bcrt"; + byte_version = address_types::TESTNET_SCRIPT; break; } - fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); - std::vector hash_data(sh.data(), sh.data() + sh.data_size()); - address = segwit_addr::encode(hrp, 0, hash_data); + + if (type == payment_type::P2WSH) { + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); + } else if (type == payment_type::P2SH_WSH) { + fc::sha256 hash256 = fc::sha256::hash(&witness_script_[0], witness_script_.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + raw_address = bytes(hash160.data(), hash160.data() + hash160.data_size()); + bytes address_bytes(1, byte_version); // 1 byte version + address_bytes.insert(address_bytes.end(), raw_address.begin(), raw_address.end()); + fc::sha256 hash256_1 = fc::sha256::hash(fc::sha256::hash(address_bytes.data(), address_bytes.size())); + address_bytes.insert(address_bytes.end(), hash256_1.data(), hash256_1.data() + 4); // 4 byte checksum + address = fc::to_base58(address_bytes); + } else { + wlog("Unsupported payment type of address"); + } } btc_one_or_m_of_n_multisig_address::btc_one_or_m_of_n_multisig_address(const fc::ecc::public_key &user_key_data, @@ -353,12 +370,12 @@ void btc_one_or_m_of_n_multisig_address::create_segwit_address() { btc_one_or_weighted_multisig_address::btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data, - bitcoin_address::network ntype) { + bitcoin_address::network ntype, payment_type type) { network_type = ntype; + this->type = type; create_redeem_script(user_key_data, keys_data); create_witness_script(); create_segwit_address(); - type = payment_type::P2WSH; } void btc_one_or_weighted_multisig_address::create_redeem_script(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data) { @@ -398,20 +415,39 @@ void btc_one_or_weighted_multisig_address::create_witness_script() { void btc_one_or_weighted_multisig_address::create_segwit_address() { std::string hrp; + address_types byte_version; switch (network_type) { case (network::mainnet): + byte_version = address_types::MAINNET_SCRIPT; hrp = "bc"; break; case (network::testnet): + byte_version = address_types::TESTNET_SCRIPT; hrp = "tb"; break; case (network::regtest): + byte_version = address_types::TESTNET_SCRIPT; hrp = "bcrt"; break; } - fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); - std::vector hash_data(sh.data(), sh.data() + sh.data_size()); - address = segwit_addr::encode(hrp, 0, hash_data); + + if (type == payment_type::P2WSH) { + fc::sha256 sh = fc::sha256::hash(&redeem_script_[0], redeem_script_.size()); + std::vector hash_data(sh.data(), sh.data() + sh.data_size()); + address = segwit_addr::encode(hrp, 0, hash_data); + } else if (type == payment_type::P2SH_WSH) { + fc::sha256 hash256 = fc::sha256::hash(&witness_script_[0], witness_script_.size()); + fc::ripemd160 hash160 = fc::ripemd160::hash(hash256.data(), hash256.data_size()); + raw_address = bytes(hash160.data(), hash160.data() + hash160.data_size()); + + bytes address_bytes(1, byte_version); // 1 byte version test net + address_bytes.insert(address_bytes.end(), raw_address.begin(), raw_address.end()); + fc::sha256 hash256_1 = fc::sha256::hash(fc::sha256::hash(address_bytes.data(), address_bytes.size())); + address_bytes.insert(address_bytes.end(), hash256_1.data(), hash256_1.data() + 4); // 4 byte checksum + address = fc::to_base58(address_bytes); + } else { + elog("Unsupported payment type of address"); + } } btc_timelocked_one_or_weighted_multisig_address::btc_timelocked_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, uint32_t latency, const std::vector> &keys_data, bitcoin_address::network ntype) : diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/estimate_fee_external.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/estimate_fee_external.cpp new file mode 100644 index 00000000..7ae350e7 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/estimate_fee_external.cpp @@ -0,0 +1,157 @@ + +#include + +#include +#include + +#include + +#include + +namespace graphene { +namespace peerplays_sidechain { + +static size_t writeFunction(void *ptr, size_t size, size_t nmemb, std::string *data) { + data->append((char *)ptr, size * nmemb); + return size * nmemb; +} + +estimate_fee_external::estimate_fee_external() { + curl = curl_easy_init(); +} + +estimate_fee_external::~estimate_fee_external() { + curl_easy_cleanup(curl); +} + +std::vector> estimate_fee_external::get_fee_external(uint16_t target_block) { + + std::vector> estimate_fee_external_collection; + this->target_block = target_block; + + for (auto &url_fee_parser : url_get_fee_parsers) { + response = get_response(url_fee_parser.first); + uint64_t fee = url_fee_parser.second(); + std::string url_str = url_fee_parser.first; + if (fee != 0) { + estimate_fee_external_collection.emplace_back(std::make_pair(url_fee_parser.first, fee)); + } + } + + return estimate_fee_external_collection; +} + +std::string estimate_fee_external::get_response(std::string url) { + + std::string response; + if (curl) { + curl_easy_setopt(curl, CURLOPT_URL, url.c_str()); + curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L); + curl_easy_setopt(curl, CURLOPT_USERPWD, "user:pass"); + curl_easy_setopt(curl, CURLOPT_USERAGENT, "curl/7.42.0"); + curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 50L); + curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); + curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, writeFunction); + curl_easy_setopt(curl, CURLOPT_WRITEDATA, &response); + curl_easy_perform(curl); + } + return response; +} + +uint64_t estimate_fee_external::parse_and_get_fee_1() { + //"https://www.bitgo.com/api/v2/btc/tx/fee" + + uint64_t founded_fee = 0; + + if (response.empty()) { + return founded_fee; + } + + std::stringstream response_ss(response); + boost::property_tree::ptree response_pt; + boost::property_tree::read_json(response_ss, response_pt); + + for (const auto &tx_child : response_pt.get_child("feeByBlockTarget")) { + const auto &block_num = tx_child.first.data(); + const auto &fee = tx_child.second.data(); + + founded_fee = std::stoi(fee); + + if (std::stoi(block_num) >= target_block) { + return founded_fee; + } + } + + return founded_fee; +} + +uint64_t estimate_fee_external::parse_and_get_fee_2() { + // https://bitcoiner.live/api/fees/estimates/latest + uint64_t founded_fee = 0; + + if (response.empty()) { + return founded_fee; + } + + std::stringstream response_ss(response); + boost::property_tree::ptree response_pt; + boost::property_tree::read_json(response_ss, response_pt); + + for (const auto &tx_child : response_pt.get_child("estimates")) { + const auto &time_str = tx_child.first.data(); + + auto time = std::stoi(time_str); + auto block_num = time / 30; + + if (tx_child.second.count("sat_per_vbyte")) { + auto founded_fee_str = tx_child.second.get_child("sat_per_vbyte").data(); + founded_fee = std::stoi(founded_fee_str) * 1000; + } + + if (block_num >= target_block) { + return founded_fee; + } + } + + return founded_fee; +} + +uint64_t estimate_fee_external::parse_and_get_fee_3() { + // https://api.blockchain.info/mempool/fees + + if (response.empty()) { + return 0; + } + + std::stringstream response_ss(response); + boost::property_tree::ptree response_pt; + boost::property_tree::read_json(response_ss, response_pt); + + if (response_pt.get_child("limits").count("min") && response_pt.get_child("limits").count("max")) { + auto limits_min_str = response_pt.get_child("limits.min").data(); + auto limits_max_str = response_pt.get_child("limits.max").data(); + + auto limits_min = std::stoi(limits_min_str); + auto limits_max = std::stoi(limits_max_str); + + auto priority_max = (limits_max - (limits_min - 1)) / 2; + + if (response_pt.count("regular") && response_pt.count("priority")) { + auto regular_str = response_pt.get_child("regular").data(); + auto priority_str = response_pt.get_child("priority").data(); + + auto regular = std::stoi(regular_str); + auto priority = std::stoi(priority_str); + + if (target_block >= priority_max) { + return regular * 1000; + } else { + return priority * 1000; + } + } + } + + return 0; +} + +}} // namespace graphene::peerplays_sidechain \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/libbitcoin_client.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/libbitcoin_client.cpp new file mode 100644 index 00000000..fdcaa98e --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin/libbitcoin_client.cpp @@ -0,0 +1,227 @@ + +#include + +#include + +#include +#include + +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +libbitcoin_client::libbitcoin_client(std::string url) : + obelisk_client(LIBBITCOIN_SERVER_TIMEOUT, LIBBITCOIN_SERVER_RETRIES) { + + std::string reg_expr = "^((?Phttps|http|tcp):\\/\\/)?(?P[a-zA-Z0-9\\-\\.]+)(:(?P\\d{1,5}))?(?P\\/.+)?"; + boost::xpressive::sregex sr = boost::xpressive::sregex::compile(reg_expr); + + boost::xpressive::smatch sm; + + if (boost::xpressive::regex_search(url, sm, sr)) { + protocol = sm["Protocol"]; + if (protocol.empty()) { + protocol = "tcp"; + } + + host = sm["Host"]; + if (host.empty()) { + host + "localhost"; + } + + port = sm["Port"]; + if (port.empty()) { + port = "9091"; + } + } + + uint16_t port_num = std::stoi(port); + std::string final_url = protocol + "://" + host; + + libbitcoin::config::endpoint address(final_url, port_num); + + libbitcoin::client::connection_type connection; + connection.retries = LIBBITCOIN_SERVER_RETRIES; + connection.server = address; + + if (!obelisk_client.connect(connection)) { + elog("Can't connect libbitcoin for url: ${url}", ("url", final_url)); + } + + is_connected = true; +} + +std::string libbitcoin_client::send_transaction(std::string tx) { + + std::string res; + + auto error_handler = [&](const std::error_code &ec) { + elog("error on sending bitcoin transaction ${error_code}", ("error_code", ec.message())); + }; + + auto result_handler = [&](libbitcoin::code result_code) { + ilog("result code on sending transaction ${result_code}", ("result_code", result_code.message())); + res = std::to_string(result_code.value()); + }; + + libbitcoin::explorer::config::transaction transaction(tx); + + libbitcoin::chain::transaction trx; + + // This validates the tx, submits it to local tx pool, and notifies peers. + obelisk_client.transaction_pool_broadcast(error_handler, result_handler, transaction); + obelisk_client.wait(); + + return res; +} + +libbitcoin::chain::output::list libbitcoin_client::get_transaction(std::string tx_id, std::string &tx_hash, uint32_t &confirmitions) { + + libbitcoin::chain::output::list outs; + + auto error_handler = [&](const std::error_code &ec) { + elog("error on fetch_trx_by_hash: ${hash} ${error_code}", ("hash", tx_id)("error_code", ec.message())); + }; + + auto transaction_handler = [&](const libbitcoin::chain::transaction &tx_handler) { + tx_hash = libbitcoin::config::hash256(tx_handler.hash(false)).to_string(); + // TODO try to find this value (confirmitions) + confirmitions = 1; + outs = tx_handler.outputs(); + }; + + libbitcoin::hash_digest hash = libbitcoin::config::hash256(tx_id); + + // obelisk_client.blockchain_fetch_transaction (error_handler, transaction_handler,hash); + obelisk_client.blockchain_fetch_transaction2(error_handler, transaction_handler, hash); + + obelisk_client.wait(); + + return outs; +} + +std::vector libbitcoin_client::listunspent(std::string address, double amount) { + std::vector result; + + auto error_handler = [&](const std::error_code &ec) { + elog("error on list_unspent ${error_code}", ("error_code", ec.message())); + }; + + auto replay_handler = [&](const libbitcoin::chain::points_value &points) { + for (auto &point : points.points) { + list_unspent_replay output; + output.hash = libbitcoin::config::hash256(point.hash()).to_string(); + output.value = point.value(); + output.index = point.index(); + result.emplace_back(output); + } + }; + + libbitcoin::wallet::payment_address payment_address(address); + uint64_t satoshi = 100000000 * amount; + + obelisk_client.blockchain_fetch_unspent_outputs(error_handler, + replay_handler, payment_address, satoshi, libbitcoin::wallet::select_outputs::algorithm::individual); + + obelisk_client.wait(); + + return result; +} + +bool libbitcoin_client::get_is_test_net() { + + bool result = false; + + auto error_handler = [&](const std::error_code &ec) { + elog("error on fetching genesis block ${error_code}", ("error_code", ec.message())); + }; + + auto block_header_handler = [&](const libbitcoin::chain::header &block_header) { + std::string hash_str = libbitcoin::config::hash256(block_header.hash()).to_string(); + if (hash_str == GENESIS_TESTNET_HASH || hash_str == GENESIS_REGTEST_HASH) { + result = true; + } + }; + + obelisk_client.blockchain_fetch_block_header(error_handler, block_header_handler, 0); + + obelisk_client.wait(); + return result; +} + +uint64_t libbitcoin_client::get_fee_from_trx(libbitcoin::chain::transaction trx) { + bool general_fee_est_error = false; + + if (trx.is_coinbase()) { + return 0; + } + + const auto total_output_value = trx.total_output_value(); + // get the inputs and from inputs previous outputs + std::map> prev_out_trxs; + for (auto &ins : trx.inputs()) { + const auto &prev_out = ins.previous_output(); + prev_out_trxs[prev_out.hash()].emplace_back(prev_out.index()); + } + + // fetch the trx to get total input value + uint64_t total_input_value = 0; + auto transaction_handler = [&](const libbitcoin::chain::transaction &tx_handler) { + std::vector indexes = prev_out_trxs[tx_handler.hash()]; + + for (auto &index : indexes) { + total_input_value += tx_handler.outputs()[index].value(); + } + }; + + auto error_handler = [&](const std::error_code &ec) { + elog("error on fetching trx ${error_code}", ("error_code", ec.message())); + general_fee_est_error = true; + }; + + for (const auto &iter : prev_out_trxs) { + if (general_fee_est_error) { + break; + } + + obelisk_client.blockchain_fetch_transaction2(error_handler, transaction_handler, iter.first); + obelisk_client.wait(); + } + + if (total_input_value >= total_output_value) { + return total_input_value - total_output_value; + } else { + // something is really wrong if this happens,so we are going to mark as an error + elog("On fee estimation something is wrong in total inputs and total outputs for trx hash: ${hash}", + ("hash", libbitcoin::config::hash256(trx.hash()).to_string())); + return 0; + } +} + +uint64_t libbitcoin_client::get_average_fee_from_trxs(std::vector trx_list) { + std::vector fee_per_trxs; + + for (auto &trx : trx_list) { + + uint64_t fee = get_fee_from_trx(trx); + if (fee > 0) { + fee_per_trxs.emplace_back(fee); + } + } + + uint64_t average_estimated_fee = 0; + + if (fee_per_trxs.size()) { + for (const auto &fee : fee_per_trxs) { + average_estimated_fee += fee; + } + + average_estimated_fee /= fee_per_trxs.size(); + } + + return average_estimated_fee; +} +}} // namespace graphene::peerplays_sidechain \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp index afad2093..033cafe0 100644 --- a/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp +++ b/libraries/plugins/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.cpp @@ -1,4 +1,5 @@ #include +#include #include @@ -6,8 +7,8 @@ namespace graphene { namespace peerplays_sidechain { namespace bitcoin { -const secp256k1_context_t *btc_context() { - static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); +const secp256k1_context *btc_context() { + static secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); return ctx; } @@ -31,20 +32,14 @@ fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &script return fc::sha256::hash(fc::sha256::hash(vec.data(), vec.size())); } -std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign) { +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context *context_sign) { bytes sig; sig.resize(72); int sig_len = sig.size(); - FC_ASSERT(secp256k1_ecdsa_sign( - context_sign, - reinterpret_cast(hash.data()), - reinterpret_cast(sig.data()), - &sig_len, - reinterpret_cast(privkey.data()), - secp256k1_nonce_function_rfc6979, - nullptr)); // TODO: replace assert with exception - + secp256k1_ecdsa_signature sign; + FC_ASSERT(secp256k1_ecdsa_sign(context_sign, &sign, (const unsigned char *)hash.data(), (const unsigned char *)privkey.data(), secp256k1_nonce_function_rfc6979, nullptr)); + FC_ASSERT(secp256k1_ecdsa_signature_serialize_der(context_sign, (unsigned char *)sig.data(), (size_t *)&sig_len, &sign)); sig.resize(sig_len); return sig; @@ -52,7 +47,7 @@ std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, con std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, const std::vector &amounts, const bytes &privkey, - const secp256k1_context_t *context_sign, int hash_type) { + const secp256k1_context *context_sign, int hash_type) { FC_ASSERT(tx.vin.size() == redeem_scripts.size() && tx.vin.size() == amounts.size()); FC_ASSERT(!privkey.empty()); @@ -77,17 +72,34 @@ void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vecto } } -bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context) { - std::vector sig_temp(sig.begin(), sig.end()); +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context *context) { + //! Get sig_temp + FC_ASSERT(sig.size() > 70); + FC_ASSERT(sig[0] == 0x30); + FC_ASSERT(sig[1] == static_cast(sig.size() - 3)); + FC_ASSERT(sig[2] == 0x02); + const uint r_size = sig[3]; + std::vector sig_temp(sig.begin() + 4 + (r_size - 32), sig.begin() + 4 + r_size); + FC_ASSERT(sig[4 + r_size] == 0x02); + const uint s_size = sig[5 + r_size]; + FC_ASSERT(sig.size() == r_size + s_size + 7); + sig_temp.insert(sig_temp.end(), sig.begin() + 6 + r_size, sig.end()); + std::vector pubkey_temp(pubkey.begin(), pubkey.end()); std::vector msg_temp(msg.begin(), msg.end()); - int result = secp256k1_ecdsa_verify(context, msg_temp.data(), sig_temp.data(), sig_temp.size(), pubkey_temp.data(), pubkey_temp.size()); + secp256k1_pubkey pub_key; + FC_ASSERT(secp256k1_ec_pubkey_parse(context, &pub_key, (const unsigned char *)pubkey_temp.data(), pubkey_temp.size())); + + secp256k1_ecdsa_signature sign; + FC_ASSERT(secp256k1_ecdsa_signature_parse_compact(context, &sign, (const unsigned char *)sig_temp.data())); + + int result = secp256k1_ecdsa_verify(context, &sign, (const unsigned char *)msg_temp.data(), &pub_key); return result == 1; } std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, - const std::vector &amounts, const secp256k1_context_t *context) { + const std::vector &amounts, const secp256k1_context *context) { FC_ASSERT(redeem_scripts.size() == amounts.size()); using data = std::pair; diff --git a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp index d08d337b..be050544 100644 --- a/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp +++ b/libraries/plugins/peerplays_sidechain/common/rpc_client.cpp @@ -1,886 +1,165 @@ #include +#include #include -#include -//#include - -#include -#include -#include +#include +#include +#include +#include +#include #include #include +#include -#include #include -#include + +#include namespace graphene { namespace peerplays_sidechain { -constexpr auto http_port = 80; -constexpr auto https_port = 443; - -template -void make_trimmed(string *str) { - boost::algorithm::trim(*str); -} - -template -void make_lower(string *str) { - boost::algorithm::to_lower(*str); -} - -bool convert_hex_to_num_helper1(const std::string &str, uint32_t *value) { - try { - size_t idx; - auto v = stol(str, &idx, 16); - if (idx != str.size()) - return false; - if (value) - *value = v; - return true; - } catch (...) { - return false; - } -} - -bool convert_dec_to_num_helper1(const std::string &str, uint32_t *value) { - try { - size_t idx; - auto v = stol(str, &idx, 10); - if (idx != str.size()) - return false; - if (value) - *value = v; - return true; - } catch (...) { - return false; - } -} - -bool convert_dec_to_num_helper1(const std::string &str, uint16_t *value) { - try { - size_t idx; - auto v = stol(str, &idx, 10); - if (idx != str.size()) - return false; - if (v > std::numeric_limits::max()) - return false; - if (value) - *value = v; - return true; - } catch (...) { - return false; - } -} - -template -constexpr V ceilDiv(V value, D divider) { - return (value + divider - 1) / divider; -} - -template -constexpr V aligned(V value, A align) { - return ceilDiv(value, align) * align; -} - -template -void reserve( - Container *container, - typename Container::size_type freeSpaceRequired, - typename Container::size_type firstAlloc, - typename Container::size_type nextAlloc) { - //TSL_ASSERT(container); - auto &c = *container; - auto required = c.size() + freeSpaceRequired; - if (c.capacity() >= required) - return; - c.reserve((firstAlloc >= required) ? firstAlloc - : firstAlloc + aligned(required - firstAlloc, nextAlloc)); -} - -template -void reserve( - Container *container, - typename Container::size_type freeSpaceRequired, - typename Container::size_type alloc) { - //TSL_ASSERT(container); - auto &c = *container; - auto required = c.size() + freeSpaceRequired; - if (c.capacity() >= required) - return; - c.reserve(aligned(required, alloc)); -} - -bool is_valid(const boost::asio::ip::tcp::endpoint &ep) { - - if (ep.port() == 0) - return false; - - if (ep.address().is_unspecified()) - return false; - - return true; -} - -// utl - -url_schema_type identify_url_schema_type(const std::string &schema_name) { - // rework - auto temp = schema_name; - make_lower(&temp); - if (temp == "http") - return url_schema_type::http; - if (temp == "https") - return url_schema_type::https; - return url_schema_type::unknown; -} - -// url_data - -url_data::url_data(const std::string &url) { - if (!parse(url)) - FC_THROW("URL parse failed"); -} - -void url_data::clear() { - schema_type = url_schema_type::unknown; - schema = decltype(schema)(); - host = decltype(host)(); - port = 0; - path = decltype(path)(); -} - -bool url_data::parse(const std::string &url) { - - typedef std::string::size_type size_t; - constexpr auto npos = std::string::npos; - - size_t schema_end = url.find("://"); - size_t host_begin; - std::string temp_schema; - - if (schema_end == npos) - host_begin = 0; // no schema - else { - if (schema_end < 3) { // schema too short: less than 3 chars - return false; - } - if (schema_end > 5) { // schema too long: more than 5 chars - return false; - } - host_begin = schema_end + 3; - temp_schema = url.substr(0, schema_end); - } - - // ASSERT(url.size() >= host_begin); - - if (url.size() == host_begin) // host is empty - return false; - - size_t port_sep = url.find(':', host_begin); - - if (port_sep == host_begin) - return false; - - size_t path_sep = url.find('/', host_begin); - - if (path_sep == host_begin) - return false; - - if ((port_sep != npos) && (path_sep != npos) && (port_sep > path_sep)) - port_sep = npos; - - std::string temp_port; - - if (port_sep != npos) { - auto port_index = port_sep + 1; - if (path_sep == npos) - temp_port = url.substr(port_index); - else - temp_port = url.substr(port_index, path_sep - port_index); - } - - if (temp_port.empty()) - port = 0; - else { - if (!convert_dec_to_num_helper1(temp_port, &port)) - return false; - } - - std::string temp_path; - - if (path_sep != npos) - temp_path = url.substr(path_sep); - - std::string temp_host; - - if (port_sep != npos) { - temp_host = url.substr(host_begin, port_sep - host_begin); - } else { - if (path_sep != npos) - temp_host = url.substr(host_begin, path_sep - host_begin); - else - temp_host = url.substr(host_begin); - } - - schema = temp_schema; - host = temp_host; - path = temp_path; - schema_type = identify_url_schema_type(schema); - - return true; -} - -}} // namespace graphene::peerplays_sidechain - -namespace graphene { namespace peerplays_sidechain { - -using namespace boost::asio; -using error_code = boost::system::error_code; -using endpoint = ip::tcp::endpoint; - -namespace detail { - -// http_call_impl - -struct tcp_socket { - - typedef ip::tcp::socket underlying_type; - - underlying_type underlying; - - tcp_socket(http_call &call) : - underlying(call.m_service) { - } - - underlying_type &operator()() { - return underlying; - } - - void connect(const http_call &call, const endpoint &ep, error_code *ec) { - // TCP connect - underlying.connect(ep, *ec); - } - - void shutdown() { - error_code ec; - underlying.close(ec); - } +struct rpc_reply { + uint16_t status; + std::string body; }; -struct ssl_socket { - - typedef ssl::stream underlying_type; - - underlying_type underlying; - - ssl_socket(http_call &call) : - underlying(call.m_service, *call.m_context) { - } - - underlying_type &operator()() { - return underlying; - } - - void connect(const http_call &call, const endpoint &ep, error_code *ec) { - - auto &u = underlying; - - // TCP connect - u.lowest_layer().connect(ep, *ec); - - // SSL connect - if (!SSL_set_tlsext_host_name(u.native_handle(), call.m_host.c_str())) - FC_THROW("SSL_set_tlsext_host_name failed"); - - u.set_verify_mode(ssl::verify_peer, *ec); - u.handshake(ssl::stream_base::client, *ec); - } - - void shutdown() { - auto &u = underlying; - error_code ec; - u.shutdown(ec); - u.lowest_layer().close(ec); - } -}; - -template -class http_call_impl { +class rpc_connection { public: - http_call_impl(http_call &call, const void *body_data, size_t body_size, const std::string &content_type_, http_response &response); - void exec(); + rpc_connection(const rpc_credentials &_credentials, bool _debug_rpc_calls); + + std::string send_post_request(std::string method, std::string params, bool show_log); + std::string get_url() const; + +protected: + rpc_credentials credentials; + bool debug_rpc_calls; + + std::string protocol; + std::string host; + std::string port; + std::string target; + std::string authorization; + + uint32_t request_id; private: - http_call &call; - const void *body_data; - size_t body_size; - std::string content_type; - http_response &response; + rpc_reply send_post_request(std::string body, bool show_log); - socket_type socket; - streambuf response_buf; - - int32_t content_length; - bool transfer_encoding_chunked; - -private: - void connect(); - void shutdown(); - void send_request(); - void on_header(std::string &name, std::string &value); - void on_header(); - void process_headers(); - void append_entity_body(std::istream *stream, size_t size); - void append_entity_body_2(std::istream *strm); - bool read_next_chunk(std::istream *strm); - void skip_footer(); - void read_body_chunked(); - void read_body_until_eof(); - void read_body_exact(); - void process_response(); + boost::beast::net::io_context ioc; + boost::beast::net::ip::tcp::resolver resolver; + boost::asio::ip::basic_resolver_results results; }; -static const char cr = 0x0D; -static const char lf = 0x0A; -static const char *crlf = "\x0D\x0A"; -static const char *crlfcrlf = "\x0D\x0A\x0D\x0A"; -static const auto crlf_uint = (((uint16_t)lf) << 8) + cr; - -template -http_call_impl::http_call_impl(http_call &call_, const void *body_data_, size_t body_size_, const std::string &content_type_, http_response &response_) : - call(call_), - body_data(body_data_), - body_size(body_size_), - content_type(content_type_), - response(response_), - socket(call), - response_buf(http_call::response_size_limit_bytes) { -} - -template -void http_call_impl::exec() { - try { - connect(); - send_request(); - process_response(); - shutdown(); - } catch (...) { - shutdown(); - throw; - } -} - -template -void http_call_impl::connect() { - - { - error_code ec; - auto &ep = call.m_endpoint; - if (is_valid(ep)) { - socket.connect(call, ep, &ec); - if (!ec) - return; - } - } - - ip::tcp::resolver resolver(call.m_service); - - auto rng = resolver.resolve(call.m_host, std::string()); - - //ASSERT(rng.begin() != rng.end()); - - error_code ec; - - for (endpoint ep : rng) { - ep.port(call.m_port); - socket.connect(call, ep, &ec); - if (!ec) { - call.m_endpoint = ep; - return; // comment to test1 - } - } - // if (!ec) return; // uncomment to test1 - - //ASSERT(ec); - throw boost::system::system_error(ec); -} - -template -void http_call_impl::shutdown() { - socket.shutdown(); -} - -template -void http_call_impl::send_request() { - - streambuf request; - std::ostream stream(&request); - - // start string: HTTP/1.0 - - //ASSERT(!call.m_path.empty()); - - stream << call.m_method << " " << call.m_path << " HTTP/1.1" << crlf; - - // host - - stream << "Host: " << call.m_host << ":" << call.m_endpoint.port() << crlf; - - // content - - if (body_size) { - stream << "Content-Type: " << content_type << crlf; - stream << "Content-Length: " << body_size << crlf; - } - - // additional headers - - const auto &h = call.m_headers; - - if (!h.empty()) { - if (h.size() < 2) - FC_THROW("invalid headers data"); - stream << h; - // ensure headers finished correctly - if ((h.substr(h.size() - 2) != crlf)) - stream << crlf; - } - - // other - - // stream << "Accept: *\x2F*" << crlf; - stream << "Accept: text/html, application/json" << crlf; - stream << "Connection: close" << crlf; - - // end - - stream << crlf; - - // send headers - - write(socket(), request); - - // send body - - if (body_size) - write(socket(), buffer(body_data, body_size)); -} - -template -void http_call_impl::on_header(std::string &name, std::string &value) { - - if (name == "content-length") { - uint32_t u; - if (!convert_dec_to_num_helper1(value, &u)) - FC_THROW("invalid content-length header data"); - content_length = u; - return; - } - - if (name == "transfer-encoding") { - boost::algorithm::to_lower(value); - if (value == "chunked") - transfer_encoding_chunked = true; - return; - } -} - -template -void http_call_impl::process_headers() { - - std::istream stream(&response_buf); - - std::string http_version; - stream >> http_version; - stream >> response.status_code; - - make_trimmed(&http_version); - make_lower(&http_version); - - if (!stream || http_version.substr(0, 6) != "http/1") - FC_THROW("invalid response data"); - - // read/skip headers - - content_length = -1; - transfer_encoding_chunked = false; - - for (;;) { - std::string header; - if (!std::getline(stream, header, lf) || (header.size() == 1 && header[0] == cr)) - break; - auto pos = header.find(':'); - if (pos == std::string::npos) - continue; - auto name = header.substr(0, pos); - make_trimmed(&name); - boost::algorithm::to_lower(name); - auto value = header.substr(pos + 1); - make_trimmed(&value); - on_header(name, value); - } -} - -template -void http_call_impl::append_entity_body(std::istream *strm, size_t size) { - if (size == 0) - return; - auto &body = response.body; - reserve(&body, size, http_call::response_first_alloc_bytes, http_call::response_next_alloc_bytes); - auto cur = body.size(); - body.resize(cur + size); - auto p = &body[cur]; - if (!strm->read(p, size)) - FC_THROW("stream read failed"); -} - -template -void http_call_impl::append_entity_body_2(std::istream *strm) { - auto avail = response_buf.size(); - if (response.body.size() + avail > http_call::response_size_limit_bytes) - FC_THROW("response body size limit exceeded"); - append_entity_body(strm, avail); -} - -template -bool http_call_impl::read_next_chunk(std::istream *strm) { - - // content length info is used as pre-alloc hint only - // it is not used inside the reading logic - - auto &buf = response_buf; - auto &stream = *strm; - auto &body = response.body; - - read_until(socket(), buf, crlf); - - std::string chunk_header; - - if (!std::getline(stream, chunk_header, lf)) - FC_THROW("failed to read chunk size"); - - auto ext_index = chunk_header.find(':'); - - if (ext_index != std::string::npos) - chunk_header.resize(ext_index); - - make_trimmed(&chunk_header); - - uint32_t chink_size; - - if (!convert_hex_to_num_helper1(chunk_header, &chink_size)) - FC_THROW("invalid chunk size string"); - - if (body.size() + chink_size > http_call::response_size_limit_bytes) - FC_THROW("response body size limit exceeded"); - - auto avail = buf.size(); - if (avail < chink_size + 2) { - auto rest = chink_size + 2 - avail; - read(socket(), buf, transfer_at_least(rest)); - } - - append_entity_body(&stream, chink_size); - - uint16_t temp; - if (!stream.read((char *)(&temp), 2)) - FC_THROW("stream read failed"); - if (temp != crlf_uint) - FC_THROW("invalid chink end"); - - return chink_size != 0; -} - -template -void http_call_impl::skip_footer() { - // to be implemeted -} - -template -void http_call_impl::read_body_chunked() { - - std::istream stream(&response_buf); - - for (;;) { - if (!read_next_chunk(&stream)) - break; - } - - skip_footer(); -} - -template -void http_call_impl::read_body_until_eof() { - - auto &buf = response_buf; - std::istream stream(&buf); - - append_entity_body_2(&stream); - - error_code ec; - - for (;;) { - auto readed = read(socket(), buf, transfer_at_least(1), ec); - append_entity_body_2(&stream); - if (ec) - break; - if (!readed) { - //ASSERT(buf.size() == 0); - FC_THROW("logic error: read failed but no error conditon"); - } - } - if ((ec != error::eof) && - (ec != ssl::error::stream_truncated)) - throw boost::system::system_error(ec); -} - -template -void http_call_impl::read_body_exact() { - - auto &buf = response_buf; - auto &body = response.body; - - auto avail = buf.size(); - - if (avail > ((size_t)content_length)) - FC_THROW("invalid response body (content length mismatch)"); - - body.resize(content_length); - - if (avail) { - if (avail != ((size_t)buf.sgetn(&body[0], avail))) - FC_THROW("stream read failed"); - } - - auto rest = content_length - avail; - - if (rest > 0) { - auto readed = read(socket(), buffer(&body[avail], rest), transfer_exactly(rest)); - //ASSERT(readed <= rest); - if (readed < rest) - FC_THROW("logic error: read failed but no error conditon"); - } -} - -template -void http_call_impl::process_response() { - - auto &buf = response_buf; - auto &body = response.body; - - read_until(socket(), buf, crlfcrlf); - - process_headers(); - - // check content length - - if (content_length >= 0) { - if (content_length < 2) { // minimum content is "{}" - FC_THROW("invalid response body (too short)"); - } - if (content_length > http_call::response_size_limit_bytes) - FC_THROW("response body size limit exceeded"); - body.reserve(content_length); - } - - if (transfer_encoding_chunked) { - read_body_chunked(); - } else { - if (content_length < 0) - read_body_until_eof(); - else { - if (content_length > 0) - read_body_exact(); - } - } -} - -} // namespace detail - -// https_call - -http_call::http_call(const url_data &url, const std::string &method, const std::string &headers) : - m_host(url.host), - m_method(method), - m_headers(headers) { - - if (url.schema_type == url_schema_type::https) { - m_context = new boost::asio::ssl::context(ssl::context::tlsv12_client); - } else { - m_context = 0; - } - - if (url.port) - m_port_default = url.port; - else { - if (url.schema_type == url_schema_type::https) - m_port_default = https_port; - else - m_port_default = http_port; - } - - m_port = m_port_default; - - set_path(url.path); - - try { - ctor_priv(); - } catch (...) { - if (m_context) - delete m_context; - throw; - } -} - -http_call::~http_call() { - if (m_context) - delete m_context; -} - -bool http_call::is_ssl() const { - return m_context != 0; -} - -const std::string &http_call::path() const { - return m_path; -} - -void http_call::set_path(const std::string &path) { - if (path.empty()) - m_path = "/"; - else - m_path = path; -} - -void http_call::set_method(const std::string &method) { - m_method = method; -} - -void http_call::set_headers(const std::string &headers) { - m_headers = headers; -} - -const std::string &http_call::host() const { - return m_host; -} - -void http_call::set_host(const std::string &host) { - m_host = host; -} - -uint16_t http_call::port() const { - return m_port; -} - -void http_call::set_port(uint16_t port) { - if (port) - m_port = port; - else - m_port = m_port_default; -} - -bool http_call::exec(const http_request &request, http_response *response) { - - //ASSERT(response); - auto &resp = *response; - m_error_what = decltype(m_error_what)(); - resp.clear(); - - try { - try { - using namespace detail; - if (!m_context) - http_call_impl(*this, request.body.data(), request.body.size(), request.content_type, resp).exec(); - else - http_call_impl(*this, request.body.data(), request.body.size(), request.content_type, resp).exec(); - return true; - } catch (const std::exception &e) { - m_error_what = e.what(); - } - } catch (...) { - m_error_what = "unknown exception"; - } - - resp.clear(); - return false; -} - -const std::string &http_call::error_what() const { - return m_error_what; -} - -void http_call::ctor_priv() { - if (m_context) { - m_context->set_default_verify_paths(); - m_context->set_options(ssl::context::default_workarounds); - } -} - -}} // namespace graphene::peerplays_sidechain - -namespace graphene { namespace peerplays_sidechain { - -rpc_client::rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug) : - debug_rpc_calls(debug), +rpc_connection::rpc_connection(const rpc_credentials &_credentials, bool _debug_rpc_calls) : + credentials(_credentials), + debug_rpc_calls(_debug_rpc_calls), request_id(0), - client(url) + resolver(ioc) { -{ + std::string reg_expr = "^((?Phttps|http):\\/\\/)?(?P[a-zA-Z0-9\\-\\.]+)(:(?P\\d{1,5}))?(?P\\/.+)?"; + boost::xpressive::sregex sr = boost::xpressive::sregex::compile(reg_expr); - client.set_method("POST"); - client.set_headers("Authorization : Basic" + fc::base64_encode(user_name + ":" + password)); + boost::xpressive::smatch sm; + + if (boost::xpressive::regex_search(credentials.url, sm, sr)) { + protocol = sm["Protocol"]; + if (protocol.empty()) { + protocol = "http"; + } + + host = sm["Host"]; + if (host.empty()) { + host + "localhost"; + } + + port = sm["Port"]; + if (port.empty()) { + port = "80"; + } + + target = sm["Target"]; + if (target.empty()) { + target = "/"; + } + + authorization = "Basic " + base64_encode(credentials.user + ":" + credentials.password); + + results = resolver.resolve(host, port); + + } else { + elog("Invalid URL: ${url}", ("url", credentials.url)); + } +} + +std::string rpc_connection::get_url() const { + return credentials.url; } std::string rpc_client::retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx) { - if (reply_str.empty()) - return std::string(); - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - if (json.find("result") == json.not_found()) { - return std::string(); - } - auto json_result = json.get_child("result"); - if (json_result.find(array_path) == json_result.not_found()) { - return std::string(); + if (reply_str.empty()) { + wlog("RPC call ${function}, empty reply string", ("function", __FUNCTION__)); + return ""; } - boost::property_tree::ptree array_ptree = json_result; - if (!array_path.empty()) { - array_ptree = json_result.get_child(array_path); - } - uint32_t array_el_idx = -1; - for (const auto &array_el : array_ptree) { - array_el_idx = array_el_idx + 1; - if (array_el_idx == idx) { - std::stringstream ss_res; - boost::property_tree::json_parser::write_json(ss_res, array_el.second); - return ss_res.str(); + try { + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") == json.not_found()) { + return ""; } + + auto json_result = json.get_child_optional("result"); + if (json_result) { + boost::property_tree::ptree array_ptree = json_result.get(); + if (!array_path.empty()) { + array_ptree = json_result.get().get_child(array_path); + } + uint32_t array_el_idx = -1; + for (const auto &array_el : array_ptree) { + array_el_idx = array_el_idx + 1; + if (array_el_idx == idx) { + std::stringstream ss_res; + boost::property_tree::json_parser::write_json(ss_res, array_el.second); + return ss_res.str(); + } + } + } + } catch (const boost::property_tree::json_parser::json_parser_error &e) { + wlog("RPC call ${function} failed: ${e}", ("function", __FUNCTION__)("e", e.what())); } - return std::string(); + return ""; } std::string rpc_client::retrieve_value_from_reply(std::string reply_str, std::string value_path) { - if (reply_str.empty()) - return std::string(); - std::stringstream ss(reply_str); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - if (json.find("result") == json.not_found()) { - return std::string(); + if (reply_str.empty()) { + wlog("RPC call ${function}, empty reply string", ("function", __FUNCTION__)); + return ""; } - auto json_result = json.get_child("result"); - if (json_result.find(value_path) == json_result.not_found()) { - return std::string(); + + try { + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") == json.not_found()) { + return ""; + } + + auto json_result = json.get_child_optional("result"); + if (json_result) { + return json_result.get().get(value_path); + } + + return json.get("result"); + } catch (const boost::property_tree::json_parser::json_parser_error &e) { + wlog("RPC call ${function} failed: ${e}", ("function", __FUNCTION__)("e", e.what())); } - return json_result.get(value_path); + + return ""; } -std::string rpc_client::send_post_request(std::string method, std::string params, bool show_log) { +std::string rpc_connection::send_post_request(std::string method, std::string params, bool show_log) { std::stringstream body; request_id = request_id + 1; @@ -893,125 +172,222 @@ std::string rpc_client::send_post_request(std::string method, std::string params body << " }"; - const auto reply = send_post_request(body.str(), show_log); + try { + const auto reply = send_post_request(body.str(), show_log); - if (reply.body.empty()) { - wlog("RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; + if (reply.body.empty()) { + wlog("RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body.str())("msg", ss.str())); + } + + if (reply.status == 200) { + return ss.str(); + } + } catch (const boost::system::system_error &e) { + elog("RPC call ${function} failed: ${e}", ("function", __FUNCTION__)("e", e.what())); } - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status_code == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body.str())("msg", ss.str())); - } return ""; } -//fc::http::reply rpc_client::send_post_request(std::string body, bool show_log) { -// fc::http::connection conn; -// conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), port)); -// -// std::string url = "http://" + ip + ":" + std::to_string(port); -// -// //if (wallet.length() > 0) { -// // url = url + "/wallet/" + wallet; -// //} -// -// fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); -// -// if (show_log) { -// ilog("### Request URL: ${url}", ("url", url)); -// ilog("### Request: ${body}", ("body", body)); -// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); -// ilog("### Response: ${ss}", ("ss", ss.str())); -// } -// -// return reply; -//} +rpc_reply rpc_connection::send_post_request(std::string body, bool show_log) { -//static size_t write_callback(char *ptr, size_t size, size_t nmemb, rpc_reply *reply) { -// size_t retval = 0; -// if (reply != nullptr) { -// reply->body.append(ptr, size * nmemb); -// retval = size * nmemb; -// } -// return retval; -//} + // These object is used as a context for ssl connection + boost::asio::ssl::context ctx(boost::asio::ssl::context::tlsv12_client); -//rpc_reply rpc_client::send_post_request(std::string body, bool show_log) { -// -// struct curl_slist *headers = nullptr; -// headers = curl_slist_append(headers, "Accept: application/json"); -// headers = curl_slist_append(headers, "Content-Type: application/json"); -// headers = curl_slist_append(headers, "charset: utf-8"); -// -// CURL *curl = curl_easy_init(); -// if (ip.find("https://", 0) != 0) { -// curl_easy_setopt(curl, CURLOPT_URL, ip.c_str()); -// curl_easy_setopt(curl, CURLOPT_PORT, port); -// } else { -// std::string full_address = ip + ":" + std::to_string(port); -// curl_easy_setopt(curl, CURLOPT_URL, full_address.c_str()); -// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, false); -// curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, false); -// } -// if (!user.empty()) { -// curl_easy_setopt(curl, CURLOPT_USERNAME, user.c_str()); -// curl_easy_setopt(curl, CURLOPT_PASSWORD, password.c_str()); -// } -// -// curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); -// curl_easy_setopt(curl, CURLOPT_POSTFIELDS, body.c_str()); -// -// //curl_easy_setopt(curl, CURLOPT_VERBOSE, true); -// -// rpc_reply reply; -// -// curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); -// curl_easy_setopt(curl, CURLOPT_WRITEDATA, &reply); -// -// curl_easy_perform(curl); -// -// curl_easy_getinfo(curl, CURLINFO_RESPONSE_CODE, &reply.status); -// -// curl_easy_cleanup(curl); -// curl_slist_free_all(headers); -// -// if (show_log) { -// std::string url = ip + ":" + std::to_string(port); -// ilog("### Request URL: ${url}", ("url", url)); -// ilog("### Request: ${body}", ("body", body)); -// std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); -// ilog("### Response: ${ss}", ("ss", ss.str())); -// } -// -// return reply; -//} + boost::beast::net::ssl::stream ssl_tcp_stream(ioc, ctx); + boost::beast::tcp_stream tcp_stream(ioc); -http_response rpc_client::send_post_request(const std::string &body, bool show_log) { - - http_request request(body, "application/json"); - http_response response; - - client.exec(request, &response); - - if (show_log) { - std::string url = client.is_ssl() ? "https" : "http"; - url = url + "://" + client.host() + ":" + std::to_string(client.port()) + client.path(); - ilog("### Request URL: ${url}", ("url", url)); - ilog("### Request: ${body}", ("body", body)); - std::stringstream ss(std::string(response.body.begin(), response.body.end())); - ilog("### Response: ${ss}", ("ss", ss.str())); + // Set SNI Hostname (many hosts need this to handshake successfully) + if (protocol == "https") { + if (!SSL_set_tlsext_host_name(ssl_tcp_stream.native_handle(), host.c_str())) { + boost::beast::error_code ec{static_cast(::ERR_get_error()), boost::asio::error::get_ssl_category()}; + throw boost::beast::system_error{ec}; + } + ctx.set_default_verify_paths(); + ctx.set_verify_mode(boost::asio::ssl::verify_peer); } - return response; + // Make the connection on the IP address we get from a lookup + if (protocol == "https") { + boost::beast::get_lowest_layer(ssl_tcp_stream).connect(results); + ssl_tcp_stream.handshake(boost::beast::net::ssl::stream_base::client); + } else { + tcp_stream.connect(results); + } + + // Set up an HTTP GET request message + boost::beast::http::request req{boost::beast::http::verb::post, target, 11}; + req.set(boost::beast::http::field::host, host + ":" + port); + req.set(boost::beast::http::field::accept, "application/json"); + req.set(boost::beast::http::field::authorization, authorization); + req.set(boost::beast::http::field::content_type, "application/json"); + req.set(boost::beast::http::field::content_encoding, "utf-8"); + req.set(boost::beast::http::field::content_length, body.length()); + req.body() = body; + + // Send the HTTP request to the remote host + if (protocol == "https") + boost::beast::http::write(ssl_tcp_stream, req); + else + boost::beast::http::write(tcp_stream, req); + + // This buffer is used for reading and must be persisted + boost::beast::flat_buffer buffer; + + // Declare a container to hold the response + boost::beast::http::response res; + + // Receive the HTTP response + if (protocol == "https") + boost::beast::http::read(ssl_tcp_stream, buffer, res); + else + boost::beast::http::read(tcp_stream, buffer, res); + + // Gracefully close the socket + boost::beast::error_code ec; + if (protocol == "https") { + boost::beast::get_lowest_layer(ssl_tcp_stream).close(); + } else { + tcp_stream.socket().shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec); + } + + // not_connected happens sometimes. Also on ssl level some servers are managing + // connecntion close, so closing here will sometimes end up with error stream truncated + // so don't bother reporting it. + if (ec && ec != boost::beast::errc::not_connected && ec != boost::asio::ssl::error::stream_truncated) + throw boost::beast::system_error{ec}; + + std::string rbody{boost::asio::buffers_begin(res.body().data()), + boost::asio::buffers_end(res.body().data())}; + rpc_reply reply; + reply.status = 200; + reply.body = rbody; + + if (show_log) { + ilog("### Request URL: ${url}", ("url", credentials.url)); + ilog("### Request: ${body}", ("body", body)); + ilog("### Response: ${rbody}", ("rbody", rbody)); + } + + return reply; +} + +rpc_client::rpc_client(sidechain_type _sidechain, const std::vector &_credentials, bool _debug_rpc_calls, bool _simulate_connection_reselection) : + sidechain(_sidechain), + debug_rpc_calls(_debug_rpc_calls), + simulate_connection_reselection(_simulate_connection_reselection) { + FC_ASSERT(_credentials.size()); + for (size_t i = 0; i < _credentials.size(); i++) + connections.push_back(new rpc_connection(_credentials[i], _debug_rpc_calls)); + n_active_conn = 0; + if (connections.size() > 1) + schedule_connection_selection(); +} + +void rpc_client::schedule_connection_selection() { + fc::time_point now = fc::time_point::now(); + static const int64_t time_to_next_conn_selection = 10 * 1000 * 1000; // 10 sec + fc::time_point next_wakeup = now + fc::microseconds(time_to_next_conn_selection); + connection_selection_task = fc::schedule([this] { + select_connection(); + }, + next_wakeup, "SON RPC connection selection"); +} + +void rpc_client::select_connection() { + FC_ASSERT(connections.size() > 1); + + const std::lock_guard lock(conn_mutex); + + static const int t_limit = 5 * 1000 * 1000, // 5 sec + quality_diff_threshold = 10 * 1000; // 10 ms + + int best_n = -1; + int best_quality = -1; + + std::vector head_block_numbers; + head_block_numbers.resize(connections.size()); + + std::vector qualities; + qualities.resize(connections.size()); + + for (size_t n = 0; n < connections.size(); n++) { + rpc_connection &conn = *connections[n]; + int quality = 0; + head_block_numbers[n] = std::numeric_limits::max(); + + // ping n'th node + if (debug_rpc_calls) + ilog("### Ping ${sidechain} node #${n}, ${url}", ("sidechain", fc::reflector::to_string(sidechain))("n", n)("url", conn.get_url())); + fc::time_point t_sent = fc::time_point::now(); + uint64_t head_block_number = ping(conn); + fc::time_point t_received = fc::time_point::now(); + int t = (t_received - t_sent).count(); + + // evaluate n'th node reply quality and switch to it if it's better + if (head_block_number != std::numeric_limits::max()) { + if (simulate_connection_reselection) + t += rand() % 10; + FC_ASSERT(t != -1); + head_block_numbers[n] = head_block_number; + if (t < t_limit) + quality = t_limit - t; // the less time, the higher quality + + // look for the best quality + if (quality > best_quality) { + best_n = n; + best_quality = quality; + } + } + qualities[n] = quality; + } + + FC_ASSERT(best_n != -1 && best_quality != -1); + if (best_n != n_active_conn) { // if the best client is not the current one, ... + uint64_t active_head_block_number = head_block_numbers[n_active_conn]; + if ((active_head_block_number == std::numeric_limits::max() // ...and the current one has no known head block... + || head_block_numbers[best_n] >= active_head_block_number) // ...or the best client's head is more recent than the current, ... + && best_quality > qualities[n_active_conn] + quality_diff_threshold) { // ...and the new client's quality exceeds current more than by threshold + n_active_conn = best_n; // ...then select new one + if (debug_rpc_calls) + ilog("### Reselected ${sidechain} node to #${n}, ${url}", ("sidechain", fc::reflector::to_string(sidechain))("n", n_active_conn)("url", connections[n_active_conn]->get_url())); + } + } + + schedule_connection_selection(); +} + +rpc_connection &rpc_client::get_active_connection() const { + return *connections[n_active_conn]; +} + +std::string rpc_client::send_post_request(std::string method, std::string params, bool show_log) { + const std::lock_guard lock(conn_mutex); + return send_post_request(get_active_connection(), method, params, show_log); +} + +std::string rpc_client::send_post_request(rpc_connection &conn, std::string method, std::string params, bool show_log) { + return conn.send_post_request(method, params, show_log); +} + +rpc_client::~rpc_client() { + try { + if (connection_selection_task.valid()) + connection_selection_task.cancel_and_wait(__FUNCTION__); + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } } }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/common/utils.cpp b/libraries/plugins/peerplays_sidechain/common/utils.cpp index 4491487f..e135afa2 100644 --- a/libraries/plugins/peerplays_sidechain/common/utils.cpp +++ b/libraries/plugins/peerplays_sidechain/common/utils.cpp @@ -1,8 +1,64 @@ #include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +const std::string base64_padding[] = {"", "==", "="}; + +std::string base64_encode(const std::string &s) { + using namespace boost::archive::iterators; + + typedef base64_from_binary> base64_enc; + + std::stringstream os; + std::copy(base64_enc(s.c_str()), base64_enc(s.c_str() + s.size()), std::ostream_iterator(os)); + os << base64_padding[s.size() % 3]; + + return os.str(); +} + +std::string base64_decode(const std::string &s) { + using namespace boost::archive::iterators; + + typedef transform_width, 8, 6> base64_dec; + + std::stringstream os; + unsigned int size = s.size(); + if (size && s[size - 1] == '=') { + --size; + if (size && s[size - 1] == '=') + --size; + } + if (size == 0) + return std::string(); + + std::copy(base64_dec(s.data()), base64_dec(s.data() + size), std::ostream_iterator(os)); + + return os.str(); +} + std::string object_id_to_string(graphene::chain::object_id_type id) { std::string object_id = fc::to_string(id.space()) + "." + fc::to_string(id.type()) + "." + fc::to_string(id.instance()); return object_id; } + +graphene::chain::object_id_type string_to_object_id(const std::string &id) { + std::vector strs; + boost::split(strs, id, boost::is_any_of(".")); + if (strs.size() != 3) { + elog("Wrong object_id format: ${id}", ("id", id)); + return graphene::chain::object_id_type{}; + } + + auto s = boost::lexical_cast(strs.at(0)); + auto t = boost::lexical_cast(strs.at(1)); + return graphene::chain::object_id_type{(uint8_t)s, (uint8_t)t, boost::lexical_cast(strs.at(2))}; +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/ethereum/decoders.cpp b/libraries/plugins/peerplays_sidechain/ethereum/decoders.cpp new file mode 100644 index 00000000..009eec69 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/ethereum/decoders.cpp @@ -0,0 +1,267 @@ +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +//! base_decoder +boost::multiprecision::uint256_t base_decoder::decode_uint256(const std::string &value) { + boost::multiprecision::uint256_t result = 0; + + boost::multiprecision::uint256_t power(1); + uint8_t digit; + int pos = value.size() - 1; + while (pos >= 0) { + digit = 0; + if ('0' <= value[pos] && value[pos] <= '9') { + digit = value[pos] - '0'; + } else if ('a' <= value[pos] && value[pos] <= 'z') { + digit = value[pos] - 'a' + 10; + } + result += digit * power; + pos--; + power *= 16; + } + + return result; +} + +std::string base_decoder::decode_address(const std::string &value) { + return value.substr(24, 40); +} + +//! deposit_erc20_decoder +const std::string deposit_erc20_decoder::function_signature = "97feb926"; //! depositERC20(address,uint256) +fc::optional deposit_erc20_decoder::decode(const std::string &input) { + const auto input_without_0x = remove_0x(input); + if (function_signature != input_without_0x.substr(0, 8)) { + return fc::optional{}; + } + if (input_without_0x.size() != 136) { + return fc::optional{}; + } + + deposit_erc20_transaction erc_20; + erc_20.token = add_0x(base_decoder::decode_address(input_without_0x.substr(8, 64))); + erc_20.amount = base_decoder::decode_uint256(input_without_0x.substr(72, 64)); + + return erc_20; +} + +//! rlp_decoder +namespace { +const signed char p_util_hexdigit[256] = + {-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, -1, -1, -1, -1, -1, -1, + -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, 0xa, 0xb, 0xc, 0xd, 0xe, 0xf, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1}; +} + +std::vector rlp_decoder::decode(const std::string &str) { + size_t consumed = 0; + const auto raw_vec = parse_hex(str); + const std::vector rlp_array = decode_rlp(raw_vec.data(), raw_vec.size(), consumed); + std::vector result_array; + for (const auto &rlp : decode_rlp(raw_vec.data(), raw_vec.size(), consumed)) { + result_array.emplace_back(bytes2hex(rlp)); + } + return result_array; +} + +std::vector rlp_decoder::decode_rlp(const unsigned char *raw, size_t len, size_t &consumed) { + std::vector rlp_result; + + consumed = 0; + + const unsigned char *end = raw + len; + const size_t prefixlen = 1; + unsigned char ch = *raw; + + if (len < 1) { + return rlp_result; + } + + // Case 1: [prefix is 1-byte data buffer] + if (ch <= 0x7f) { + const unsigned char *tok_start = raw; + const unsigned char *tok_end = tok_start + prefixlen; + FC_ASSERT(tok_end <= end); + + // parsing done; assign data buffer value. + const std::vector buf{tok_start, tok_end}; + rlp_result.emplace_back(buf.cbegin(), buf.cend()); + + consumed = buf.size(); + } + // Case 2: [prefix, including buffer length][data] + else if ((ch >= 0x80) && (ch <= 0xb7)) { + const size_t blen = ch - 0x80; + const size_t expected = prefixlen + blen; + + if (len < expected) + return std::vector{}; + + const unsigned char *tok_start = raw + 1; + const unsigned char *tok_end = tok_start + blen; + FC_ASSERT(tok_end <= end); + + // require minimal encoding + if ((blen == 1) && (tok_start[0] <= 0x7f)) + return std::vector{}; + + // parsing done; assign data buffer value. + const std::vector buf{tok_start, tok_end}; + rlp_result.emplace_back(buf.cbegin(), buf.cend()); + + consumed = expected; + } + // Case 3: [prefix][buffer length][data] + else if ((ch >= 0xb8) && (ch <= 0xbf)) { + const size_t uintlen = ch - 0xb7; + size_t expected = prefixlen + uintlen; + + if (len < expected) + return std::vector{}; + + FC_ASSERT(uintlen > 0 && uintlen <= RLP_maxUintLen); + + const unsigned char *tok_start = raw + prefixlen; + if ((uintlen > 1) && (tok_start[0] == 0)) // no leading zeroes + return std::vector{}; + + // read buffer length + const uint64_t slen = to_int(tok_start, uintlen); + + // validate buffer length, including possible addition overflows. + expected = prefixlen + uintlen + slen; + if ((slen < (RLP_listStart - RLP_bufferLenStart - RLP_maxUintLen)) || (expected > len) || (slen > len)) + return std::vector{}; + + // parsing done; assign data buffer value. + tok_start = raw + prefixlen + uintlen; + const unsigned char *tok_end = tok_start + slen; + const std::vector buf{tok_start, tok_end}; + rlp_result.emplace_back(buf.cbegin(), buf.cend()); + + consumed = expected; + } + // Case 4: [prefix][list] + else if ((ch >= 0xc0) && (ch <= 0xf7)) { + const size_t payloadlen = ch - 0xc0; + const size_t expected = prefixlen + payloadlen; + + // read list payload + const auto array = decode_array(raw, len, 0, payloadlen); + rlp_result.insert(rlp_result.end(), array.cbegin(), array.cend()); + + consumed = expected; + } + // Case 5: [prefix][list length][list] + else { + FC_ASSERT((ch >= 0xf8) && (ch <= 0xff)); + + const size_t uintlen = ch - 0xf7; + const size_t expected = prefixlen + uintlen; + + if (len < expected) + return std::vector{}; + + FC_ASSERT(uintlen > 0 && uintlen <= RLP_maxUintLen); + + const unsigned char *tok_start = raw + prefixlen; + if ((uintlen > 1) && (tok_start[0] == 0)) // no leading zeroes + return std::vector{}; + + // read list length + const size_t payloadlen = to_int(tok_start, uintlen); + + // special requirement for non-immediate length + if (payloadlen < (0x100 - RLP_listStart - RLP_maxUintLen)) + return std::vector{}; + + // read list payload + const auto array = decode_array(raw, len, uintlen, payloadlen); + rlp_result.insert(rlp_result.end(), array.cbegin(), array.cend()); + + consumed = prefixlen + uintlen + payloadlen; + } + + return rlp_result; +} + +std::vector rlp_decoder::decode_array(const unsigned char *raw, size_t len, size_t uintlen, size_t payloadlen) { + std::vector rlp_result; + const size_t prefixlen = 1; + + // validate list length, including possible addition overflows. + const size_t expected = prefixlen + uintlen + payloadlen; + if ((expected > len) || (payloadlen > len)) + return std::vector{}; + + size_t child_len = payloadlen; + const unsigned char *list_ent = raw + prefixlen + uintlen; + + // recursively read until payloadlen bytes parsed, or error + while (child_len > 0) { + size_t child_consumed = 0; + + const auto val = decode_rlp(list_ent, child_len, child_consumed); + rlp_result.insert(rlp_result.end(), val.cbegin(), val.cend()); + + list_ent += child_consumed; + child_len -= child_consumed; + } + + return rlp_result; +} + +uint64_t rlp_decoder::to_int(const unsigned char *raw, size_t len) { + if (len == 0) + return 0; + else if (len == 1) + return *raw; + else + return (raw[len - 1]) + (to_int(raw, len - 1) * 256); +} + +std::vector rlp_decoder::parse_hex(const std::string &str) { + return parse_hex(str.c_str()); +} + +std::vector rlp_decoder::parse_hex(const char *psz) { + // convert hex dump to vector + std::vector vch; + while (true) { + while (isspace(*psz)) + psz++; + signed char c = hex_digit(*psz++); + if (c == (signed char)-1) + break; + unsigned char n = (c << 4); + c = hex_digit(*psz++); + if (c == (signed char)-1) + break; + n |= c; + vch.push_back(n); + } + return vch; +} + +signed char rlp_decoder::hex_digit(char c) { + return p_util_hexdigit[(unsigned char)c]; +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/ethereum/encoders.cpp b/libraries/plugins/peerplays_sidechain/ethereum/encoders.cpp new file mode 100644 index 00000000..19a373de --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/ethereum/encoders.cpp @@ -0,0 +1,171 @@ +#include + +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +//! base_encoder +std::string base_encoder::encode_uint256(boost::multiprecision::uint256_t value) { + return (boost::format("%x") % boost::io::group(std::setw(64), std::setfill('0'), value)).str(); +} + +std::string base_encoder::encode_address(const std::string &value) { + return (boost::format("%x") % boost::io::group(std::setw(64), std::setfill('0'), value)).str(); +} + +std::string base_encoder::encode_string(const std::string &value) { + std::string data = (boost::format("%x") % boost::io::group(std::setw(64), std::setfill('0'), value.size())).str(); + data += boost::algorithm::hex(value); + if (value.size() % 32 != 0) { + data += std::string((64 - value.size() * 2 % 64), '0'); + } + return data; +} + +//! update_owners_encoder +const std::string update_owners_encoder::function_signature = "23ab6adf"; //! updateOwners((address,uint256)[],string) +std::string update_owners_encoder::encode(const std::vector> &owners_weights, const std::string &object_id) { + std::string data = add_0x(function_signature); + data += base_encoder::encode_uint256(64); + data += base_encoder::encode_uint256((owners_weights.size() * 2 + 3) * 32); + data += base_encoder::encode_uint256(owners_weights.size()); + for (const auto &owner : owners_weights) { + data += base_encoder::encode_address(owner.first); + data += base_encoder::encode_uint256(owner.second); + } + data += base_encoder::encode_string(object_id); + + return data; +} + +//! withdrawal_encoder +const std::string withdrawal_encoder::function_signature = "e088747b"; //! withdraw(address,uint256,string) +std::string withdrawal_encoder::encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id) { + std::string data = add_0x(function_signature); + data += base_encoder::encode_address(to); + data += base_encoder::encode_uint256(amount); + data += base_encoder::encode_uint256(32 * 3); + data += base_encoder::encode_string(object_id); + + return data; +} + +//! withdrawal_erc20_encoder +const std::string withdrawal_erc20_encoder::function_signature = "483c0467"; //! withdrawERC20(address,address,uint256,string) +std::string withdrawal_erc20_encoder::encode(const std::string &token, const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id) { + std::string data = add_0x(function_signature); + data += base_encoder::encode_address(token); + data += base_encoder::encode_address(to); + data += base_encoder::encode_uint256(amount); + data += base_encoder::encode_uint256(32 * 4); + data += base_encoder::encode_string(object_id); + + return data; +} + +//! signature_encoder +const std::string update_owners_function_signature = "9d608673"; //! updateOwners((bytes,(uint8,bytes32,bytes32))[]) +const std::string withdrawal_function_signature = "daac6c81"; //! withdraw((bytes,(uint8,bytes32,bytes32))[]) +const std::string withdrawal_erc20_function_signature = "d2bf2866"; //! withdrawERC20((bytes,(uint8,bytes32,bytes32))[]) +signature_encoder::signature_encoder(const std::string &function_hash) : + function_signature{function_hash} { +} + +std::string signature_encoder::get_function_signature_from_transaction(const std::string &transaction) { + const std::string tr = remove_0x(transaction); + if (tr.substr(0, 8) == update_owners_encoder::function_signature) + return update_owners_function_signature; + + if (tr.substr(0, 8) == withdrawal_encoder::function_signature) + return withdrawal_function_signature; + + if (tr.substr(0, 8) == withdrawal_erc20_encoder::function_signature) + return withdrawal_erc20_function_signature; + + return ""; +} + +std::string signature_encoder::encode(const std::vector &transactions) const { + std::string data = add_0x(function_signature); + data += base_encoder::encode_uint256(32); + data += base_encoder::encode_uint256(transactions.size()); + size_t offset = (transactions.size()) * 32; + for (const auto &transaction : transactions) { + data += base_encoder::encode_uint256(offset); + const auto transaction_data = remove_0x(transaction.data); + offset += 5 * 32 + transaction_data.size() / 2; + if (transaction_data.size() / 2 % 32 != 0) { + offset += 32 - transaction_data.size() / 2 % 32; + } + } + for (const auto &transaction : transactions) { + data += base_encoder::encode_uint256(4 * 32); + data += base_encoder::encode_address(transaction.sign.v); + data += base_encoder::encode_address(transaction.sign.r); + data += base_encoder::encode_address(transaction.sign.s); + const auto transaction_data = remove_0x(transaction.data); + data += base_encoder::encode_uint256(transaction_data.size() / 2); + data += transaction_data; + if (transaction_data.size() % 64 != 0) { + data += std::string((64 - transaction_data.size() % 64), '0'); + } + } + + return data; +} + +//! rlp_encoder +std::string rlp_encoder::encode(const std::string &s) { + return encode_rlp(hex2bytes(s)); +} + +std::string rlp_encoder::encode_length(int len, int offset) { + if (len < 56) { + std::string temp; + temp = (char)(len + offset); + return temp; + } else { + const std::string hexLength = to_hex(len); + const int lLength = hexLength.size() / 2; + const std::string fByte = to_hex(offset + 55 + lLength); + return hex2bytes(fByte + hexLength); + } +} + +std::string rlp_encoder::hex2bytes(const std::string &s) { + std::string dest; + const auto s_final = s.size() % 2 == 0 ? s : "0" + s; + dest.resize(s_final.size() / 2); + hex2bin(s_final.c_str(), &dest[0]); + return dest; +} + +std::string rlp_encoder::encode_rlp(const std::string &s) { + if (s.size() == 1 && (unsigned char)s[0] < 128) + return s; + else + return encode_length(s.size(), 128) + s; +} + +int rlp_encoder::char2int(char input) { + if (input >= '0' && input <= '9') + return input - '0'; + if (input >= 'A' && input <= 'F') + return input - 'A' + 10; + if (input >= 'a' && input <= 'f') + return input - 'a' + 10; + + return -1; +} + +void rlp_encoder::hex2bin(const char *src, char *target) { + while (*src && src[1]) { + *(target++) = char2int(*src) * 16 + char2int(src[1]); + src += 2; + } +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/ethereum/transaction.cpp b/libraries/plugins/peerplays_sidechain/ethereum/transaction.cpp new file mode 100644 index 00000000..9a1383df --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/ethereum/transaction.cpp @@ -0,0 +1,243 @@ +#include + +#include +#include +#include +#include + +#include +#include + +#include +#include + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +const secp256k1_context *eth_context() { + static secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +bytes keccak_hash(const std::string &data) { + bytes hash; + hash.resize(32); + const auto transaction_string = boost::algorithm::unhex(remove_0x(data)); + keccak_256((const unsigned char *)transaction_string.data(), transaction_string.size(), (unsigned char *)hash.data()); + + return hash; +} + +signature sign_hash(const bytes &hash, const std::string &chain_id, const std::string &private_key) { + const bytes priv_key = parse_hex(private_key); + + int recid = 0; + secp256k1_ecdsa_recoverable_signature sig; + FC_ASSERT(secp256k1_ecdsa_sign_recoverable(eth_context(), &sig, (const unsigned char *)hash.data(), (const unsigned char *)priv_key.data(), NULL, NULL)); + fc::ecc::compact_signature result; + FC_ASSERT(secp256k1_ecdsa_recoverable_signature_serialize_compact(eth_context(), (unsigned char *)result.begin() + 1, &recid, &sig)); + + unsigned int v = recid + from_hex(chain_id) * 2 + 35; + + bytes r; + for (int i = 1; i < 33; i++) + r.emplace_back((char)result.at(i)); + + bytes s; + for (int i = 33; i < 65; i++) + s.emplace_back((char)result.at(i)); + + signature eth_sig; + eth_sig.v = to_hex(v); + eth_sig.r = fc::to_hex((char *)&r[0], r.size()); + eth_sig.s = fc::to_hex((char *)&s[0], s.size()); + + return eth_sig; +} + +//! base_transaction + +base_transaction::base_transaction(const std::string &raw_tx) { +} + +//! transaction + +transaction::transaction(const std::string &raw_tx) : + base_transaction{raw_tx} { + deserialize(raw_tx); +} + +const transaction &transaction::sign(const std::string &private_key) const { + return *this; +} + +std::string transaction::serialize() const { + boost::property_tree::ptree pt; + pt.put("from", from); + pt.put("to", to); + pt.put("data", data); + + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, pt); + return ss.str(); +} + +void transaction::deserialize(const std::string &raw_tx) { + std::stringstream ss_tx(raw_tx); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(ss_tx, tx_json); + + if (tx_json.count("from")) + from = tx_json.get("from"); + if (tx_json.count("to")) + to = tx_json.get("to"); + if (tx_json.count("data")) + data = tx_json.get("data"); +} + +//! raw_transaction + +raw_transaction::raw_transaction(const std::string &raw_tx) : + base_transaction{raw_tx} { + deserialize(raw_tx); +} + +bytes raw_transaction::hash() const { + return keccak_hash(serialize()); +} + +signed_transaction raw_transaction::sign(const std::string &private_key) const { + //! Prepare signed transaction + signed_transaction tr; + tr.nonce = nonce; + tr.gas_price = gas_price; + tr.gas_limit = gas_limit; + tr.to = to; + tr.value = value; + tr.data = data; + + const auto sig = sign_hash(hash(), chain_id, private_key); + tr.v = sig.v; + tr.r = sig.r; + tr.s = sig.s; + + return tr; +} + +std::string raw_transaction::serialize() const { + const std::string serialized = rlp_encoder::encode(remove_0x(nonce)) + + rlp_encoder::encode(remove_0x(gas_price)) + + rlp_encoder::encode(remove_0x(gas_limit)) + + rlp_encoder::encode(remove_0x(to)) + + rlp_encoder::encode(remove_0x(value)) + + rlp_encoder::encode(remove_0x(data)) + + rlp_encoder::encode(remove_0x(chain_id)) + + rlp_encoder::encode("") + + rlp_encoder::encode(""); + + return add_0x(bytes2hex(rlp_encoder::encode_length(serialized.size(), 192) + serialized)); +} + +void raw_transaction::deserialize(const std::string &raw_tx) { + const auto rlp_array = rlp_decoder::decode(remove_0x(raw_tx)); + FC_ASSERT(rlp_array.size() >= 7, "Wrong rlp format"); + + nonce = !rlp_array.at(0).empty() ? add_0x(rlp_array.at(0)) : add_0x("0"); + boost::algorithm::to_lower(nonce); + gas_price = add_0x(rlp_array.at(1)); + boost::algorithm::to_lower(gas_price); + gas_limit = add_0x(rlp_array.at(2)); + boost::algorithm::to_lower(gas_limit); + to = add_0x(rlp_array.at(3)); + boost::algorithm::to_lower(to); + value = !rlp_array.at(4).empty() ? add_0x(rlp_array.at(4)) : add_0x("0"); + boost::algorithm::to_lower(value); + data = !rlp_array.at(5).empty() ? add_0x(rlp_array.at(5)) : ""; + boost::algorithm::to_lower(data); + chain_id = add_0x(rlp_array.at(6)); + boost::algorithm::to_lower(chain_id); +} + +//! signed_transaction + +signed_transaction::signed_transaction(const std::string &raw_tx) : + base_transaction{raw_tx} { + deserialize(raw_tx); +} + +std::string signed_transaction::recover(const std::string &chain_id) const { + fc::ecc::compact_signature input64; + fc::from_hex(r, (char *)&input64.at(1), 32); + const int recid = from_hex(v) - from_hex(chain_id) * 2 - 35; + fc::from_hex(std::to_string(recid), (char *)&input64.at(0), 1); + fc::from_hex(s, (char *)&input64.at(33), 32); + + secp256k1_ecdsa_recoverable_signature sig; + FC_ASSERT(secp256k1_ecdsa_recoverable_signature_parse_compact(eth_context(), &sig, (const unsigned char *)&input64.data[1], recid)); + + raw_transaction tr; + tr.nonce = nonce; + tr.gas_price = gas_price; + tr.gas_limit = gas_limit; + tr.to = to; + tr.value = value; + tr.data = data; + tr.chain_id = chain_id; + + secp256k1_pubkey rawPubkey; + FC_ASSERT(secp256k1_ecdsa_recover(eth_context(), &rawPubkey, &sig, (const unsigned char *)tr.hash().data())); + + std::array pubkey; + size_t biglen = 65; + FC_ASSERT(secp256k1_ec_pubkey_serialize(eth_context(), pubkey.data(), &biglen, &rawPubkey, SECP256K1_EC_UNCOMPRESSED)); + + const std::string out = std::string(pubkey.begin(), pubkey.end()).substr(1); + bytes hash; + hash.resize(32); + keccak_256((const unsigned char *)out.data(), out.size(), (unsigned char *)hash.data()); + + return add_0x(fc::to_hex((char *)&hash[0], hash.size()).substr(24)); +} + +std::string signed_transaction::serialize() const { + const std::string serialized = rlp_encoder::encode(remove_0x(nonce)) + + rlp_encoder::encode(remove_0x(gas_price)) + + rlp_encoder::encode(remove_0x(gas_limit)) + + rlp_encoder::encode(remove_0x(to)) + + rlp_encoder::encode(remove_0x(value)) + + rlp_encoder::encode(remove_0x(data)) + + rlp_encoder::encode(remove_0x(v)) + + rlp_encoder::encode(remove_leading_00(remove_0x(r))) + + rlp_encoder::encode(remove_leading_00(remove_0x(s))); + + return add_0x(bytes2hex(rlp_encoder::encode_length(serialized.size(), 192) + serialized)); +} + +void signed_transaction::deserialize(const std::string &raw_tx) { + const auto rlp_array = rlp_decoder::decode(remove_0x(raw_tx)); + FC_ASSERT(rlp_array.size() >= 9, "Wrong rlp format"); + + nonce = !rlp_array.at(0).empty() ? add_0x(rlp_array.at(0)) : add_0x("0"); + boost::algorithm::to_lower(nonce); + gas_price = add_0x(rlp_array.at(1)); + boost::algorithm::to_lower(gas_price); + gas_limit = add_0x(rlp_array.at(2)); + boost::algorithm::to_lower(gas_limit); + to = add_0x(rlp_array.at(3)); + boost::algorithm::to_lower(to); + value = !rlp_array.at(4).empty() ? add_0x(rlp_array.at(4)) : add_0x("0"); + boost::algorithm::to_lower(value); + data = !rlp_array.at(5).empty() ? add_0x(rlp_array.at(5)) : ""; + boost::algorithm::to_lower(data); + v = add_0x(rlp_array.at(6)); + boost::algorithm::to_lower(v); + r = add_0x(add_leading_00(rlp_array.at(7))); + boost::algorithm::to_lower(r); + s = add_0x(add_leading_00(rlp_array.at(8))); + boost::algorithm::to_lower(s); +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/ethereum/types.cpp b/libraries/plugins/peerplays_sidechain/ethereum/types.cpp new file mode 100644 index 00000000..e33e0c4a --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/ethereum/types.cpp @@ -0,0 +1,36 @@ +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +signature::signature(const std::string &sign) { + deserialize(sign); +} + +std::string signature::serialize() const { + boost::property_tree::ptree pt; + pt.put("v", v); + pt.put("r", r); + pt.put("s", s); + + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, pt); + return ss.str(); +} + +void signature::deserialize(const std::string &raw_tx) { + std::stringstream ss_tx(raw_tx); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(ss_tx, tx_json); + + if (tx_json.count("v")) + v = tx_json.get("v"); + if (tx_json.count("r")) + r = tx_json.get("r"); + if (tx_json.count("s")) + s = tx_json.get("s"); +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/ethereum/utils.cpp b/libraries/plugins/peerplays_sidechain/ethereum/utils.cpp new file mode 100644 index 00000000..0f34cd9c --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/ethereum/utils.cpp @@ -0,0 +1,72 @@ +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +bytes parse_hex(const std::string &str) { + bytes vec(str.size() / 2); + fc::from_hex(str, vec.data(), vec.size()); + return vec; +} + +std::string bytes2hex(const std::string &s) { + std::string dest; + for (const auto &i : s) + dest += uchar2Hex((unsigned char)i); + + return dest; +} + +std::string uchar2Hex(unsigned char n) { + std::string dest; + dest.resize(2); + sprintf(&dest[0], "%X", n); + + if (n < (unsigned char)16) { + dest[1] = dest[0]; + dest[0] = '0'; + } + + return dest; +} + +std::string add_0x(const std::string &s) { + if (s.size() > 1) { + if (s.substr(0, 2) == "0x") + return s; + } + + return "0x" + s; +} + +std::string remove_0x(const std::string &s) { + if (s.size() > 1) { + if (s.substr(0, 2) == "0x") + return s.substr(2); + } + + return s; +} + +std::string add_leading_00(const std::string &s) { + std::string result = s; + + while (result.size() < 64) { + result = "00" + result; + } + + return result; +} + +std::string remove_leading_00(const std::string &s) { + std::string result = s; + + while (result.size() > 1 && result.substr(0, 2) == "00") { + result = result.substr(2); + } + + return result; +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp index b9467e2f..04762352 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/bitcoin_address.hpp @@ -8,6 +8,14 @@ using namespace graphene::chain; namespace graphene { namespace peerplays_sidechain { namespace bitcoin { const bytes op_num = {0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5a, 0x5b, 0x5c, 0x5d, 0x5e, 0x5f}; // OP_1 - OP_15 +enum address_types { MAINNET_SCRIPT = 5, + TESTNET_SCRIPT = 196 }; + +enum script_op { + OP_0 = 0x00, + OP_PUSH = 0x20, + OP_SIZE_34 = 0x22 +}; class bitcoin_address { @@ -96,9 +104,6 @@ private: void create_address(); public: - enum address_types { MAINNET_SCRIPT = 5, - TESTNET_SCRIPT = 196 }; - enum { OP_0 = 0x00, OP_EQUAL = 0x87, OP_HASH160 = 0xa9, @@ -145,7 +150,7 @@ public: btc_weighted_multisig_address() = default; btc_weighted_multisig_address(const std::vector> &keys_data, - network network_type = network::regtest); + network network_type = network::regtest, payment_type type = payment_type::P2SH_WSH); bytes get_redeem_script() const { return redeem_script_; @@ -190,7 +195,7 @@ class btc_one_or_weighted_multisig_address : public bitcoin_address { public: btc_one_or_weighted_multisig_address() = default; btc_one_or_weighted_multisig_address(const fc::ecc::public_key &user_key_data, const std::vector> &keys_data, - network network_type = network::regtest); + network network_type = network::regtest, payment_type type = payment_type::P2SH_WSH); bytes get_redeem_script() const { return redeem_script_; } diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/estimate_fee_external.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/estimate_fee_external.hpp new file mode 100644 index 00000000..fdfcc73f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/estimate_fee_external.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include + +#include +#include +#include +#include +#include + +typedef std::function get_fee_func_type; + +namespace graphene { namespace peerplays_sidechain { + +class estimate_fee_external { +public: + estimate_fee_external(); + ~estimate_fee_external(); + std::vector> get_fee_external(uint16_t target_block); + +private: + std::string get_response(std::string url); + // Here add your custom parser for external url. Take care of incremental name + // and populate the list of url_parsers bellow paired with the function + uint64_t parse_and_get_fee_1(); + uint64_t parse_and_get_fee_2(); + uint64_t parse_and_get_fee_3(); + + const std::map url_get_fee_parsers{ + {"https://www.bitgo.com/api/v2/btc/tx/fee", std::bind(&estimate_fee_external::parse_and_get_fee_1, this)}, + {"https://bitcoiner.live/api/fees/estimates/latest", std::bind(&estimate_fee_external::parse_and_get_fee_2, this)}, + {"https://api.blockchain.info/mempool/fees", std::bind(&estimate_fee_external::parse_and_get_fee_3, this)}}; + + std::string response; + uint16_t target_block; + CURL *curl{nullptr}; +}; + +}} // namespace graphene::peerplays_sidechain \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/libbitcoin_client.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/libbitcoin_client.hpp new file mode 100644 index 00000000..4426983a --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/libbitcoin_client.hpp @@ -0,0 +1,53 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#define LIBBITCOIN_SERVER_TIMEOUT (10) +#define LIBBITCOIN_SERVER_RETRIES (100) +#define DEAFULT_LIBBITCOIN_TRX_FEE (20000) +#define MAX_TRXS_IN_MEMORY_POOL (30000) +#define MIN_TRXS_IN_BUCKET (100) + +#define GENESIS_MAINNET_HASH "000000000019d6689c085ae165831e934ff763ae46a2a6c172b3f1b60a8ce26f" +#define GENESIS_TESTNET_HASH "000000000933ea01ad0ee984209779baaec3ced90fa3f408719526f8d77f4943" +#define GENESIS_REGTEST_HASH "0f9188f13cb7b2c71f2a335e3a4fc328bf5beb436012afca590b1a11466e2206" + +namespace graphene { namespace peerplays_sidechain { + +typedef std::function + block_update_handler; + +struct list_unspent_replay { + std::string hash; + uint64_t value; + uint32_t index; +}; + +class libbitcoin_client { +public: + libbitcoin_client(std::string url); + std::string send_transaction(const std::string tx); + libbitcoin::chain::output::list get_transaction(std::string tx_id, std::string &tx_hash, uint32_t &confirmitions); + std::vector listunspent(std::string address, double amount); + uint64_t get_average_fee_from_trxs(std::vector trx_list); + uint64_t get_fee_from_trx(libbitcoin::chain::transaction trx); + bool get_is_test_net(); + +private: + libbitcoin::client::obelisk_client obelisk_client; + libbitcoin::protocol::zmq::identifier id; + + std::string protocol; + std::string host; + std::string port; + std::string url; + + bool is_connected = false; +}; + +}} // namespace graphene::peerplays_sidechain \ No newline at end of file diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp index 41808562..34f8eb89 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/sign_bitcoin_transaction.hpp @@ -8,23 +8,23 @@ namespace graphene { namespace peerplays_sidechain { namespace bitcoin { class bitcoin_transaction; -const secp256k1_context_t *btc_context(); +const secp256k1_context *btc_context(); fc::sha256 get_signature_hash(const bitcoin_transaction &tx, const bytes &scriptPubKey, int64_t amount, size_t in_index, int hash_type, bool is_witness); -std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context_t *context_sign = nullptr); +std::vector privkey_sign(const bytes &privkey, const fc::sha256 &hash, const secp256k1_context *context_sign = nullptr); std::vector sign_witness_transaction_part(const bitcoin_transaction &tx, const std::vector &redeem_scripts, const std::vector &amounts, const bytes &privkey, - const secp256k1_context_t *context_sign = nullptr, int hash_type = 1); + const secp256k1_context *context_sign = nullptr, int hash_type = 1); void sign_witness_transaction_finalize(bitcoin_transaction &tx, const std::vector &redeem_scripts, bool use_mulisig_workaround = true); -bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context_t *context); +bool verify_sig(const bytes &sig, const bytes &pubkey, const bytes &msg, const secp256k1_context *context); std::vector> sort_sigs(const bitcoin_transaction &tx, const std::vector &redeem_scripts, - const std::vector &amounts, const secp256k1_context_t *context); + const std::vector &amounts, const secp256k1_context *context); void add_signatures_to_transaction_multisig(bitcoin_transaction &tx, std::vector> &signature_set); diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp index 4e1c4b58..5f582eb1 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/utils.hpp @@ -1,4 +1,5 @@ #pragma once + #include #include #include diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp index 63d218ee..bdec60d1 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/rpc_client.hpp @@ -3,134 +3,52 @@ #include #include -#include -#include +#include +#include -//#include +#include +#include + +#include namespace graphene { namespace peerplays_sidechain { -enum class url_schema_type { unknown, - http, - https, +class rpc_connection; + +struct rpc_credentials { + std::string url; + std::string user; + std::string password; }; -// utl - -url_schema_type identify_url_schema_type(const std::string &schema_name); - -struct url_data { - - url_schema_type schema_type; - std::string schema; - std::string host; - uint16_t port; - std::string path; - - url_data() : - schema_type(url_schema_type::unknown), - port(0) { - } - - url_data(const std::string &url); - - void clear(); - - bool parse(const std::string &url); -}; - -struct http_request { - - std::string body; - std::string content_type; - - http_request(const std::string &body_, const std::string &content_type_) : - body(body_), - content_type(content_type_) { - } -}; - -struct http_response { - - uint16_t status_code; - std::string body; - - void clear() { - status_code = 0; - body = decltype(body)(); - } -}; - -namespace detail { -template -class http_call_impl; -class tcp_socket; -class ssl_socket; -} // namespace detail - -class http_call { -public: - http_call(const url_data &url, const std::string &method = std::string(), const std::string &headers = std::string()); - ~http_call(); - - bool is_ssl() const; - - const std::string &path() const; - void set_path(const std::string &path); - void set_method(const std::string &method); - void set_headers(const std::string &headers); - const std::string &host() const; - void set_host(const std::string &host); - - uint16_t port() const; - void set_port(uint16_t port); - - bool exec(const http_request &request, http_response *response); - - const std::string &error_what() const; - -private: - template - friend class detail::http_call_impl; - friend detail::tcp_socket; - friend detail::ssl_socket; - static constexpr auto response_size_limit_bytes = 16 * 1024 * 1024; - static constexpr auto response_first_alloc_bytes = 32 * 1024; - static constexpr auto response_next_alloc_bytes = 256 * 1024; - std::string m_host; - uint16_t m_port_default; - uint16_t m_port; - std::string m_path; - std::string m_method; - std::string m_headers; - std::string m_error_what; - - boost::asio::io_service m_service; - boost::asio::ssl::context *m_context; - boost::asio::ip::tcp::endpoint m_endpoint; - - void ctor_priv(); -}; - -}} // namespace graphene::peerplays_sidechain - -namespace graphene { namespace peerplays_sidechain { - class rpc_client { public: - rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug); + const sidechain_type sidechain; + + rpc_client(sidechain_type _sidechain, const std::vector &_credentials, bool _debug_rpc_calls, bool _simulate_connection_reselection); + ~rpc_client(); protected: - std::string retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx); - std::string retrieve_value_from_reply(std::string reply_str, std::string value_path); + bool debug_rpc_calls; + bool simulate_connection_reselection; std::string send_post_request(std::string method, std::string params, bool show_log); - bool debug_rpc_calls; - uint32_t request_id; + static std::string send_post_request(rpc_connection &conn, std::string method, std::string params, bool show_log); + + static std::string retrieve_array_value_from_reply(std::string reply_str, std::string array_path, uint32_t idx); + static std::string retrieve_value_from_reply(std::string reply_str, std::string value_path); private: - http_call client; - http_response send_post_request(const std::string &body, bool show_log); + std::vector connections; + int n_active_conn; + fc::future connection_selection_task; + std::mutex conn_mutex; + + rpc_connection &get_active_connection() const; + + void select_connection(); + void schedule_connection_selection(); + virtual uint64_t ping(rpc_connection &conn) const = 0; }; }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/utils.hpp index 99c59019..5b0f3f7d 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/utils.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/common/utils.hpp @@ -2,4 +2,12 @@ #include +namespace graphene { namespace peerplays_sidechain { + +std::string base64_encode(const std::string &s); +std::string base64_decode(const std::string &s); + std::string object_id_to_string(graphene::chain::object_id_type id); +graphene::chain::object_id_type string_to_object_id(const std::string &id); + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp index 70618236..b3063a99 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp @@ -57,10 +57,16 @@ struct info_for_vin { bool resend = false; }; +enum class sidechain_event_type { + deposit, + withdrawal +}; + struct sidechain_event_data { fc::time_point_sec timestamp; uint32_t block_num; sidechain_type sidechain; + sidechain_event_type type; std::string sidechain_uid; std::string sidechain_transaction_id; std::string sidechain_from; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/decoders.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/decoders.hpp new file mode 100644 index 00000000..68bd325b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/decoders.hpp @@ -0,0 +1,49 @@ +#pragma once + +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +class base_decoder { +public: + static boost::multiprecision::uint256_t decode_uint256(const std::string &value); + static std::string decode_address(const std::string &value); +}; + +struct deposit_erc20_transaction { + std::string token; + boost::multiprecision::uint256_t amount; +}; + +class deposit_erc20_decoder { +public: + static const std::string function_signature; + + static fc::optional decode(const std::string &input); +}; + +class rlp_decoder { +private: + enum RLP_constants { + RLP_maxUintLen = 8, + RLP_bufferLenStart = 0x80, + RLP_listStart = 0xc0, + }; + +public: + static std::vector decode(const std::string &str); + +private: + static std::vector decode_rlp(const unsigned char *raw, size_t len, size_t &consumed); + static std::vector decode_array(const unsigned char *raw, size_t len, size_t uintlen, size_t payloadlen); + static uint64_t to_int(const unsigned char *raw, size_t len); + static std::vector parse_hex(const std::string &str); + static std::vector parse_hex(const char *psz); + static signed char hex_digit(char c); +}; + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/encoders.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/encoders.hpp new file mode 100644 index 00000000..43a2a862 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/encoders.hpp @@ -0,0 +1,67 @@ +#pragma once + +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +struct encoded_sign_transaction { + std::string data; + signature sign; +}; + +class base_encoder { +public: + static std::string encode_uint256(boost::multiprecision::uint256_t value); + static std::string encode_address(const std::string &value); + static std::string encode_string(const std::string &value); +}; + +class update_owners_encoder { +public: + static const std::string function_signature; + + static std::string encode(const std::vector> &owners_weights, const std::string &object_id); +}; + +class withdrawal_encoder { +public: + static const std::string function_signature; + + static std::string encode(const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id); +}; + +class withdrawal_erc20_encoder { +public: + static const std::string function_signature; + + static std::string encode(const std::string &token, const std::string &to, boost::multiprecision::uint256_t amount, const std::string &object_id); +}; + +class signature_encoder { +public: + const std::string function_signature; + + signature_encoder(const std::string &function_hash); + + static std::string get_function_signature_from_transaction(const std::string &transaction); + + std::string encode(const std::vector &transactions) const; +}; + +class rlp_encoder { +public: + static std::string encode(const std::string &s); + static std::string encode_length(int len, int offset); + static std::string hex2bytes(const std::string &s); + +private: + static std::string encode_rlp(const std::string &s); + static int char2int(char input); + static void hex2bin(const char *src, char *target); +}; + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp new file mode 100644 index 00000000..77ef444b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/transaction.hpp @@ -0,0 +1,80 @@ +#pragma once + +#include +#include +#include + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +bytes keccak_hash(const std::string &data); +signature sign_hash(const bytes &hash, const std::string &chain_id, const std::string &private_key); + +class base_transaction { +public: + base_transaction() = default; + base_transaction(const std::string &raw_tx); + + virtual std::string serialize() const = 0; + virtual void deserialize(const std::string &raw_tx) = 0; +}; + +class transaction : base_transaction { +public: + std::string from; + std::string to; + std::string data; + + transaction() = default; + transaction(const std::string &raw_tx); + + const transaction &sign(const std::string &private_key) const; + + virtual std::string serialize() const override; + virtual void deserialize(const std::string &raw_tx) override; +}; + +class signed_transaction; +class raw_transaction : base_transaction { +public: + std::string nonce; + std::string gas_price; + std::string gas_limit; + std::string to; + std::string value; + std::string data; + std::string chain_id; + + raw_transaction() = default; + raw_transaction(const std::string &raw_tx); + + bytes hash() const; + signed_transaction sign(const std::string &private_key) const; + + virtual std::string serialize() const override; + virtual void deserialize(const std::string &raw_tx) override; +}; + +class signed_transaction : base_transaction { +public: + std::string nonce; + std::string gas_price; + std::string gas_limit; + std::string to; + std::string value; + std::string data; + std::string v; + std::string r; + std::string s; + + signed_transaction() = default; + signed_transaction(const std::string &raw_tx); + + std::string recover(const std::string &chain_id) const; + + virtual std::string serialize() const override; + virtual void deserialize(const std::string &raw_tx) override; +}; + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/types.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/types.hpp new file mode 100644 index 00000000..3acc8b8f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/types.hpp @@ -0,0 +1,25 @@ +#pragma once + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +typedef uint64_t chain_id_type; +typedef uint64_t network_id_type; + +using bytes = std::vector; + +class signature { +public: + std::string v; + std::string r; + std::string s; + + signature() = default; + signature(const std::string &sign); + + std::string serialize() const; + void deserialize(const std::string &sign); +}; + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/utils.hpp new file mode 100644 index 00000000..4ac70094 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/ethereum/utils.hpp @@ -0,0 +1,43 @@ +#pragma once + +#include + +namespace graphene { namespace peerplays_sidechain { namespace ethereum { + +bytes parse_hex(const std::string &str); + +std::string bytes2hex(const std::string &s); + +std::string uchar2Hex(unsigned char n); + +std::string add_0x(const std::string &s); + +std::string remove_0x(const std::string &s); + +std::string add_leading_00(const std::string &s); + +std::string remove_leading_00(const std::string &s); + +template +std::string to_hex(const T &val, bool add_front_zero = true) { + std::stringstream stream; + stream << std::hex << val; + std::string result(stream.str()); + if (add_front_zero) { + if (result.size() % 2) + result = "0" + result; + } + return result; +} + +template +T from_hex(const std::string &s) { + T val; + std::stringstream stream; + stream << std::hex << s; + stream >> val; + + return val; +} + +}}} // namespace graphene::peerplays_sidechain::ethereum diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index 92591d0a..d890ae68 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -13,16 +13,18 @@ class peerplays_sidechain_plugin_impl; } struct son_proposal_type { - son_proposal_type(int op, son_id_type son, object_id_type object) : + son_proposal_type(int op, sidechain_type sid, son_id_type son, object_id_type object) : op_type(op), + sidechain(sid), son_id(son), object_id(object) { } int op_type; + sidechain_type sidechain; son_id_type son_id; object_id_type object_id; bool operator<(const son_proposal_type &other) const { - return std::tie(op_type, son_id, object_id) < std::tie(other.op_type, other.son_id, other.object_id); + return std::tie(op_type, sidechain, son_id, object_id) < std::tie(other.op_type, other.sidechain, other.son_id, other.object_id); } }; @@ -42,16 +44,17 @@ public: std::unique_ptr my; std::set &get_sons(); - const son_id_type get_current_son_id(); - const son_object get_current_son_object(); + const son_id_type get_current_son_id(sidechain_type sidechain); + const son_object get_current_son_object(sidechain_type sidechain); const son_object get_son_object(son_id_type son_id); - bool is_active_son(son_id_type son_id); + bool is_active_son(sidechain_type sidechain, son_id_type son_id); bool is_son_deregistered(son_id_type son_id); fc::ecc::private_key get_private_key(son_id_type son_id); fc::ecc::private_key get_private_key(chain::public_key_type public_key); - void log_son_proposal_retry(int op_type, object_id_type object_id); - bool can_son_participate(int op_type, object_id_type object_id); + void log_son_proposal_retry(sidechain_type sidechain, int op_type, object_id_type object_id); + bool can_son_participate(sidechain_type sidechain, int op_type, object_id_type object_id); std::map> get_son_listener_log(); + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain); }; }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_api.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_api.hpp index b4636537..5d6df6af 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_api.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_api.hpp @@ -26,9 +26,10 @@ public: std::shared_ptr my; std::map> get_son_listener_log(); + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain); }; }} // namespace graphene::peerplays_sidechain FC_API(graphene::peerplays_sidechain::sidechain_api, - (get_son_listener_log)) + (get_son_listener_log)(estimate_withdrawal_transaction_fee)) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp index a9257e54..85dbc1f6 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp @@ -16,14 +16,17 @@ namespace graphene { namespace peerplays_sidechain { class sidechain_net_handler { +protected: + sidechain_net_handler(sidechain_type _sidechain, peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + public: - sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); virtual ~sidechain_net_handler(); - sidechain_type get_sidechain(); - std::vector get_sidechain_deposit_addresses(); - std::vector get_sidechain_withdraw_addresses(); - std::string get_private_key(std::string public_key); + sidechain_type get_sidechain() const; + std::vector get_sidechain_deposit_addresses() const; + std::vector get_sidechain_withdraw_addresses() const; + std::vector get_sidechain_transaction_objects(sidechain_transaction_status status) const; + std::string get_private_key(std::string public_key) const; bool proposal_exists(int32_t operation_tag, const object_id_type &object_id, boost::optional proposal_op = boost::none); bool signer_expected(const sidechain_transaction_object &sto, son_id_type signer); @@ -50,13 +53,15 @@ public: void add_to_son_listener_log(std::string trx_id); std::vector get_son_listener_log(); + virtual optional estimate_withdrawal_transaction_fee() const = 0; protected: + const sidechain_type sidechain; peerplays_sidechain_plugin &plugin; graphene::chain::database &database; - sidechain_type sidechain; bool debug_rpc_calls; + bool use_bitcoind_client; std::map private_keys; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp index 9c067e0e..96e8ec9c 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -2,15 +2,19 @@ #include +#include #include +#include #include #include -#include - #include + #include +#include +#include +#include namespace graphene { namespace peerplays_sidechain { @@ -21,7 +25,27 @@ public: uint64_t amount_; }; -class bitcoin_rpc_client { +class btc_txin { +public: + std::vector tx_address; + uint64_t tx_amount; + uint64_t tx_vout; +}; + +class btc_tx { +public: + std::string tx_txid; + uint32_t tx_confirmations; + std::vector tx_in_list; +}; + +class block_data { +public: + std::string block_hash; + libbitcoin::chain::block block; +}; + +class bitcoin_client_base { public: enum class multi_type { script, @@ -39,71 +63,140 @@ public: std::string label; }; -public: - bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password, bool _debug_rpc_calls); + virtual uint64_t estimatesmartfee(uint16_t conf_target = 1) = 0; + virtual std::vector getblock(const block_data &block, int32_t verbosity = 2) = 0; + virtual btc_tx getrawtransaction(const std::string &txid, const bool verbose = false) = 0; + virtual void getnetworkinfo() = 0; + virtual std::string getblockchaininfo() = 0; + virtual std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999) = 0; + virtual std::string sendrawtransaction(const std::string &tx_hex) = 0; + virtual void importmulti(const std::vector &address_or_script_array, const bool rescan = true) { + ; + }; + virtual std::string loadwallet(const std::string &filename) { + return ""; + }; + virtual std::string walletlock() { + return ""; + }; + virtual bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60) { + return false; + }; - std::string addmultisigaddress(const uint32_t nrequired, const std::vector public_keys); - std::string combinepsbt(const vector &psbts); - std::string createmultisig(const uint32_t nrequired, const std::vector public_keys); - std::string createpsbt(const std::vector &ins, const fc::flat_map outs); - std::string createrawtransaction(const std::vector &ins, const fc::flat_map outs); - std::string createwallet(const std::string &wallet_name); - std::string decodepsbt(std::string const &tx_psbt); - std::string decoderawtransaction(std::string const &tx_hex); - std::string encryptwallet(const std::string &passphrase); - uint64_t estimatesmartfee(uint16_t conf_target = 128); - std::string finalizepsbt(std::string const &tx_psbt); - std::string getaddressinfo(const std::string &address); - std::string getblock(const std::string &block_hash, int32_t verbosity = 2); - std::string getrawtransaction(const std::string &txid, const bool verbose = false); - std::string getnetworkinfo(); - std::string gettransaction(const std::string &txid, const bool include_watch_only = false); + void import_trx_to_memory_pool(const libbitcoin::chain::transaction &trx) { + std::unique_lock lck(libbitcoin_event_mutex); + if (trx_memory_pool.size() < MAX_TRXS_IN_MEMORY_POOL) { + trx_memory_pool.emplace_back(trx); + } + } + +protected: + std::vector trx_memory_pool; + std::mutex libbitcoin_event_mutex; +}; + +class bitcoin_rpc_client : public bitcoin_client_base, public rpc_client { +public: +public: + bitcoin_rpc_client(const std::vector &_credentials, bool _debug_rpc_calls, bool _simulate_connection_reselection); + + uint64_t estimatesmartfee(uint16_t conf_target = 1); + std::vector getblock(const block_data &block, int32_t verbosity = 2); + btc_tx getrawtransaction(const std::string &txid, const bool verbose = false); + void getnetworkinfo(); std::string getblockchaininfo(); - void importaddress(const std::string &address_or_script, const std::string &label = "", const bool rescan = true, const bool p2sh = false); void importmulti(const std::vector &address_or_script_array, const bool rescan = true); std::vector listunspent(const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999); std::string loadwallet(const std::string &filename); std::string sendrawtransaction(const std::string &tx_hex); - std::string signrawtransactionwithwallet(const std::string &tx_hash); - std::string unloadwallet(const std::string &filename); std::string walletlock(); - std::string walletprocesspsbt(std::string const &tx_psbt); bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); -private: - fc::http::reply send_post_request(std::string body, bool show_log); + virtual uint64_t ping(rpc_connection &conn) const override; +private: std::string ip; - uint32_t rpc_port; std::string user; std::string password; - std::string wallet; + std::string wallet_name; std::string wallet_password; - bool debug_rpc_calls; + uint32_t bitcoin_major_version; +}; - fc::http::header authorization; +class bitcoin_libbitcoin_client : public bitcoin_client_base, public libbitcoin_client { +public: + bitcoin_libbitcoin_client(std::string url); + uint64_t estimatesmartfee(uint16_t conf_target = 1); + std::vector getblock(const block_data &block, int32_t verbosity = 2); + btc_tx getrawtransaction(const std::string &txid, const bool verbose = false); + void getnetworkinfo(); + std::string getblockchaininfo(); + std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf = 1, const uint32_t maxconf = 9999999); + std::string sendrawtransaction(const std::string &tx_hex); + +private: + bool is_test_net = false; + std::unique_ptr estimate_fee_ext; + uint64_t current_internal_fee = DEAFULT_LIBBITCOIN_TRX_FEE; }; // ============================================================================= -class zmq_listener { +class zmq_listener_base { public: - zmq_listener(std::string _ip, uint32_t _zmq); + virtual ~zmq_listener_base(){}; + zmq_listener_base(std::string _ip, uint32_t _block_zmq_port, uint32_t _trx_zmq_port = 0) { + ip = _ip; + block_zmq_port = _block_zmq_port; + trx_zmq_port = _trx_zmq_port; + stopped = false; + }; + virtual void start() = 0; + boost::signals2::signal block_event_received; + boost::signals2::signal trx_event_received; - boost::signals2::signal event_received; +protected: + std::string ip; + uint32_t block_zmq_port; + uint32_t trx_zmq_port; + std::atomic_bool stopped; + std::thread block_thr; + std::thread trx_thr; +}; + +class zmq_listener : public zmq_listener_base { +public: + zmq_listener(std::string _ip, uint32_t _block_zmq_port, uint32_t _trx_zmq_port = 0); + virtual ~zmq_listener(); + void start(); private: void handle_zmq(); std::vector receive_multipart(); - std::string ip; - uint32_t zmq_port; - zmq::context_t ctx; zmq::socket_t socket; }; +class zmq_listener_libbitcoin : public zmq_listener_base { +public: + zmq_listener_libbitcoin(std::string _ip, uint32_t _block_zmq_port = 9093, uint32_t _trx_zmq_port = 9094); + virtual ~zmq_listener_libbitcoin(); + void start(); + +private: + void handle_block(); + void handle_trx(); + + libbitcoin::protocol::zmq::context block_context; + libbitcoin::protocol::zmq::socket block_socket; + libbitcoin::protocol::zmq::poller block_poller; + libbitcoin::protocol::zmq::context trx_context; + libbitcoin::protocol::zmq::socket trx_socket; + libbitcoin::protocol::zmq::poller trx_poller; +}; + // ============================================================================= class sidechain_net_handler_bitcoin : public sidechain_net_handler { @@ -119,27 +212,29 @@ public: std::string process_sidechain_transaction(const sidechain_transaction_object &sto); std::string send_sidechain_transaction(const sidechain_transaction_object &sto); bool settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount); + virtual optional estimate_withdrawal_transaction_fee() const override; private: - std::string ip; - uint32_t zmq_port; - uint32_t rpc_port; - uint32_t bitcoin_major_version; - std::string rpc_user; - std::string rpc_password; - std::string wallet; + std::vector _rpc_credentials; + std::string libbitcoin_server_ip; + uint32_t libbitcoin_block_zmq_port; + uint32_t libbitcoin_trx_zmq_port; + uint32_t bitcoin_node_zmq_port; + std::string wallet_name; std::string wallet_password; - std::unique_ptr bitcoin_client; - std::unique_ptr listener; + std::unique_ptr bitcoin_client; + std::unique_ptr listener; fc::future on_changed_objects_task; + bitcoin::bitcoin_address::network network_type; + uint32_t bitcoin_major_version; std::mutex event_handler_mutex; typedef std::lock_guard scoped_lock; - std::string create_primary_wallet_address(const std::vector &son_pubkeys); + std::string create_primary_wallet_address(const std::vector &son_pubkeys); std::string create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address); std::string create_deposit_transaction(const son_wallet_deposit_object &swdo); @@ -149,9 +244,9 @@ private: std::string sign_transaction(const sidechain_transaction_object &sto); std::string send_transaction(const sidechain_transaction_object &sto); - void handle_event(const std::string &event_data); + void block_handle_event(const block_data &event_data); + void trx_handle_event(const libbitcoin::chain::transaction &event_data); std::string get_redeemscript_for_userdeposit(const std::string &user_address); - std::vector extract_info_from_block(const std::string &_block); void on_changed_objects(const vector &ids, const flat_set &accounts); void on_changed_objects_cb(const vector &ids, const flat_set &accounts); }; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_ethereum.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_ethereum.hpp new file mode 100644 index 00000000..fcabccf5 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_ethereum.hpp @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include + +#include +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class ethereum_rpc_client : public rpc_client { +public: + ethereum_rpc_client(const std::vector &credentials, bool debug_rpc_calls, bool simulate_connection_reselection); + + std::string eth_blockNumber(); + std::string eth_get_block_by_number(std::string block_number, bool full_block); + std::string eth_get_logs(std::string wallet_contract_address); + std::string eth_chainId(); + std::string net_version(); + std::string eth_get_transaction_count(const std::string ¶ms); + std::string eth_gas_price(); + std::string eth_estimateGas(const std::string ¶ms); + + std::string get_chain_id(); + std::string get_network_id(); + std::string get_nonce(const std::string &address); + std::string get_gas_price(); + std::string get_gas_limit(); + std::string get_estimate_gas(const std::string ¶ms); + + std::string eth_send_transaction(const std::string ¶ms); + std::string eth_send_raw_transaction(const std::string ¶ms); + std::string eth_get_transaction_receipt(const std::string ¶ms); + std::string eth_get_transaction_by_hash(const std::string ¶ms); + + virtual uint64_t ping(rpc_connection &conn) const override; +}; + +class sidechain_net_handler_ethereum : public sidechain_net_handler { +public: + sidechain_net_handler_ethereum(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler_ethereum(); + + bool process_proposal(const proposal_object &po); + void process_primary_wallet(); + void process_sidechain_addresses(); + bool process_deposit(const son_wallet_deposit_object &swdo); + bool process_withdrawal(const son_wallet_withdraw_object &swwo); + std::string process_sidechain_transaction(const sidechain_transaction_object &sto); + std::string send_sidechain_transaction(const sidechain_transaction_object &sto); + bool settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount); + virtual optional estimate_withdrawal_transaction_fee() const override; + +private: + std::vector _rpc_credentials; + std::string wallet_contract_address; + using bimap_type = boost::bimap; + bimap_type erc20_addresses; + + ethereum_rpc_client *rpc_client; + + ethereum::chain_id_type chain_id; + ethereum::network_id_type network_id; + + std::string create_primary_wallet_transaction(const std::vector &son_pubkeys, const std::string &object_id); + std::string create_deposit_transaction(const son_wallet_deposit_object &swdo); + std::string create_withdrawal_transaction(const son_wallet_withdraw_object &swwo); + + std::string sign_transaction(const sidechain_transaction_object &sto); + + uint64_t last_block_received; + fc::future _listener_task; + boost::signals2::signal event_received; + void schedule_ethereum_listener(); + void ethereum_listener_loop(); + void handle_event(const std::string &block_number); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_factory.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_factory.hpp new file mode 100644 index 00000000..3aa42894 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_factory.hpp @@ -0,0 +1,23 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler_factory { +public: + sidechain_net_handler_factory(peerplays_sidechain_plugin &_plugin); + + std::unique_ptr create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options) const; + +private: + peerplays_sidechain_plugin &plugin; +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp index 47e6bf90..baa95bbf 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_hive.hpp @@ -6,15 +6,14 @@ #include -#include #include #include namespace graphene { namespace peerplays_sidechain { -class hive_node_rpc_client : public rpc_client { +class hive_rpc_client : public rpc_client { public: - hive_node_rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug_rpc_calls); + hive_rpc_client(const std::vector &credentials, bool debug_rpc_calls, bool simulate_connection_reselection); std::string account_history_api_get_transaction(std::string transaction_id); std::string block_api_get_block(uint32_t block_number); @@ -31,6 +30,8 @@ public: std::string get_head_block_time(); std::string get_is_test_net(); std::string get_last_irreversible_block_num(); + + virtual uint64_t ping(rpc_connection &conn) const override; }; class sidechain_net_handler_hive : public sidechain_net_handler { @@ -46,12 +47,14 @@ public: std::string process_sidechain_transaction(const sidechain_transaction_object &sto); std::string send_sidechain_transaction(const sidechain_transaction_object &sto); bool settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount); + virtual optional estimate_withdrawal_transaction_fee() const override; private: - std::string node_rpc_url; - std::string node_rpc_user; - std::string node_rpc_password; - hive_node_rpc_client *node_rpc_client; + std::vector _rpc_credentials; + + std::string wallet_account_name; + + hive_rpc_client *rpc_client; hive::chain_id_type chain_id; hive::network network_type; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp index 69eea1a9..139db7bf 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp @@ -19,6 +19,7 @@ public: std::string process_sidechain_transaction(const sidechain_transaction_object &sto); std::string send_sidechain_transaction(const sidechain_transaction_object &sto); bool settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount); + virtual optional estimate_withdrawal_transaction_fee() const override; private: }; diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp deleted file mode 100644 index 8bfda125..00000000 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp +++ /dev/null @@ -1,38 +0,0 @@ -#pragma once - -#include -#include -#include - -#include - -#include - -namespace graphene { namespace peerplays_sidechain { - -class sidechain_net_manager { -public: - sidechain_net_manager(peerplays_sidechain_plugin &_plugin); - virtual ~sidechain_net_manager(); - - bool create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options); - void process_proposals(); - void process_active_sons_change(); - void create_deposit_addresses(); - void process_deposits(); - void process_withdrawals(); - void process_sidechain_transactions(); - void send_sidechain_transactions(); - void settle_sidechain_transactions(); - - std::map> get_son_listener_log(); - -private: - peerplays_sidechain_plugin &plugin; - graphene::chain::database &database; - std::vector> net_handlers; - - void on_applied_block(const signed_block &b); -}; - -}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index ed80fbfc..98ebbbf2 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -3,6 +3,8 @@ #include #include #include +#include +#include #include #include @@ -11,7 +13,7 @@ #include #include #include -#include +#include #include namespace bpo = boost::program_options; @@ -33,36 +35,37 @@ public: void plugin_shutdown(); std::set &get_sons(); - const son_id_type get_current_son_id(); - const son_object get_current_son_object(); + const son_id_type get_current_son_id(sidechain_type sidechain); + const son_object get_current_son_object(sidechain_type sidechain); const son_object get_son_object(son_id_type son_id); - bool is_active_son(son_id_type son_id); + bool is_active_son(sidechain_type sidechain, son_id_type son_id); bool is_son_deregistered(son_id_type son_id); bool is_son_deregister_op_valid(const chain::operation &op); bool is_son_down_op_valid(const chain::operation &op); bool is_valid_son_proposal(const chain::proposal_object &proposal); fc::ecc::private_key get_private_key(son_id_type son_id); fc::ecc::private_key get_private_key(chain::public_key_type public_key); - void log_son_proposal_retry(int op_type, object_id_type object_id); - bool can_son_participate(int op_type, object_id_type object_id); + void log_son_proposal_retry(sidechain_type sidechain, int op_type, object_id_type object_id); + bool can_son_participate(sidechain_type sidechain, int op_type, object_id_type object_id); std::map> get_son_listener_log(); + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain); void schedule_heartbeat_loop(); void heartbeat_loop(); void schedule_son_processing(); - void son_processing(); - void approve_proposals(); - void create_son_down_proposals(); - void create_son_deregister_proposals(); + void son_processing(sidechain_type sidechain); + void approve_proposals(sidechain_type sidechain); + void create_son_down_proposals(sidechain_type sidechain); + void create_son_deregister_proposals(sidechain_type sidechain); - void process_proposals(); - void process_active_sons_change(); - void create_deposit_addresses(); - void process_deposits(); - void process_withdrawals(); - void process_sidechain_transactions(); - void send_sidechain_transactions(); - void settle_sidechain_transactions(); + void process_proposals(sidechain_type sidechain); + void process_active_sons_change(sidechain_type sidechain); + void create_deposit_addresses(sidechain_type sidechain); + void process_deposits(sidechain_type sidechain); + void process_withdrawals(sidechain_type sidechain); + void process_sidechain_transactions(sidechain_type sidechain); + void send_sidechain_transactions(sidechain_type sidechain); + void settle_sidechain_transactions(sidechain_type sidechain); private: peerplays_sidechain_plugin &plugin; @@ -80,17 +83,24 @@ private: bool sidechain_enabled_hive; bool sidechain_enabled_peerplays; - son_id_type current_son_id; + std::map current_son_id; + std::mutex current_son_id_mutex; + std::mutex access_db_mutex; + std::mutex access_approve_prop_mutex; + std::mutex access_son_down_prop_mutex; + std::mutex access_son_deregister_prop_mutex; - std::unique_ptr net_manager; + std::map sidechain_enabled; + std::map> net_handlers; std::set sons; std::map private_keys; fc::future _heartbeat_task; - fc::future _son_processing_task; + std::map> _son_processing_task; std::map son_retry_count; - uint16_t retries_threshold; + uint16_t retries_threshold = 150; bool first_block_skipped; + bool son_processing_enabled; void on_applied_block(const signed_block &b); }; @@ -105,9 +115,29 @@ peerplays_sidechain_plugin_impl::peerplays_sidechain_plugin_impl(peerplays_sidec sidechain_enabled_ethereum(false), sidechain_enabled_hive(false), sidechain_enabled_peerplays(false), - current_son_id(son_id_type(std::numeric_limits().max())), - net_manager(nullptr), - first_block_skipped(false) { + current_son_id([] { + std::map current_son_id; + for (const auto &active_sidechain_type : all_sidechain_types) { + current_son_id.emplace(active_sidechain_type, son_id_type(std::numeric_limits().max())); + } + return current_son_id; + }()), + sidechain_enabled([] { + std::map sidechain_enabled; + for (const auto &active_sidechain_type : all_sidechain_types) { + sidechain_enabled.emplace(active_sidechain_type, false); + } + return sidechain_enabled; + }()), + net_handlers([] { + std::map> net_handlers; + for (const auto &active_sidechain_type : all_sidechain_types) { + net_handlers.emplace(active_sidechain_type, nullptr); + } + return net_handlers; + }()), + first_block_skipped(false), + son_processing_enabled(false) { } peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() { @@ -121,8 +151,10 @@ peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() { } try { - if (_son_processing_task.valid()) - _son_processing_task.cancel_and_wait(__FUNCTION__); + for (const auto &active_sidechain_type : all_sidechain_types) { + if (_son_processing_task.count(active_sidechain_type) != 0 && _son_processing_task.at(active_sidechain_type).valid()) + _son_processing_task.at(active_sidechain_type).wait(); + } } catch (fc::canceled_exception &) { //Expected exception. Move along. } catch (fc::exception &e) { @@ -145,22 +177,38 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( cli.add_options()("sidechain-retry-threshold", bpo::value()->default_value(150), "Sidechain retry throttling threshold"); cli.add_options()("debug-rpc-calls", bpo::value()->default_value(false), "Outputs RPC calls to console"); + cli.add_options()("simulate-rpc-connection-reselection", bpo::value()->default_value(false), "Simulate RPC connection reselection by altering their response times by a random value"); cli.add_options()("bitcoin-sidechain-enabled", bpo::value()->default_value(false), "Bitcoin sidechain handler enabled"); - cli.add_options()("bitcoin-node-ip", bpo::value()->default_value("127.0.0.1"), "IP address of Bitcoin node"); + cli.add_options()("bitcoin-node-ip", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR("127.0.0.1"), "IP address of Bitcoin node"); + cli.add_options()("use-bitcoind-client", bpo::value()->default_value(false), "Use bitcoind client instead of libbitcoin client"); + cli.add_options()("libbitcoin-server-ip", bpo::value()->default_value("127.0.0.1"), "Libbitcoin server IP address"); + cli.add_options()("libbitcoin-server-block-zmq-port", bpo::value()->default_value(9093), "Block ZMQ port of libbitcoin server"); + cli.add_options()("libbitcoin-server-trx-zmq-port", bpo::value()->default_value(9094), "Trx ZMQ port of libbitcoin server"); cli.add_options()("bitcoin-node-zmq-port", bpo::value()->default_value(11111), "ZMQ port of Bitcoin node"); cli.add_options()("bitcoin-node-rpc-port", bpo::value()->default_value(8332), "RPC port of Bitcoin node"); cli.add_options()("bitcoin-node-rpc-user", bpo::value()->default_value("1"), "Bitcoin RPC user"); cli.add_options()("bitcoin-node-rpc-password", bpo::value()->default_value("1"), "Bitcoin RPC password"); - cli.add_options()("bitcoin-wallet", bpo::value(), "Bitcoin wallet"); + cli.add_options()("bitcoin-wallet-name", bpo::value(), "Bitcoin wallet name"); cli.add_options()("bitcoin-wallet-password", bpo::value(), "Bitcoin wallet password"); cli.add_options()("bitcoin-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")), "Tuple of [Bitcoin public key, Bitcoin private key] (may specify multiple times)"); + cli.add_options()("ethereum-sidechain-enabled", bpo::value()->default_value(false), "Ethereum sidechain handler enabled"); + cli.add_options()("ethereum-node-rpc-url", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR("127.0.0.1:8545"), "Ethereum node RPC URL [http[s]://]host[:port]"); + cli.add_options()("ethereum-node-rpc-user", bpo::value(), "Ethereum RPC user"); + cli.add_options()("ethereum-node-rpc-password", bpo::value(), "Ethereum RPC password"); + cli.add_options()("ethereum-wallet-contract-address", bpo::value(), "Ethereum wallet contract address"); + cli.add_options()("ethereum-erc-20-address", bpo::value>()->composing()->multitoken(), + "Tuple of [ERC-20 symbol, ERC-20 address] (may specify multiple times)"); + cli.add_options()("ethereum-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", "9bedac2bd8fe2a6f6528e066c67fc8ac0622e96828d40c0e820d83c5bd2b0589")), + "Tuple of [Ethereum public key, Ethereum private key] (may specify multiple times)"); + cli.add_options()("hive-sidechain-enabled", bpo::value()->default_value(false), "Hive sidechain handler enabled"); - cli.add_options()("hive-node-rpc-url", bpo::value()->default_value("127.0.0.1:28090"), "Hive node RPC URL [http[s]://]host[:port]"); + cli.add_options()("hive-node-rpc-url", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR("127.0.0.1:28090"), "Hive node RPC URL [http[s]://]host[:port]"); cli.add_options()("hive-node-rpc-user", bpo::value(), "Hive node RPC user"); cli.add_options()("hive-node-rpc-password", bpo::value(), "Hive node RPC password"); + cli.add_options()("hive-wallet-account-name", bpo::value(), "Hive wallet account name"); cli.add_options()("hive-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("TST6LLegbAgLAy28EHrffBVuANFWcFgmqRMW13wBmTExqFE9SCkg4", "5JNHfZYKGaomSFvd4NUdQ9qMcEAC43kujbfjueTHpVapX1Kzq2n")), "Tuple of [Hive public key, Hive private key] (may specify multiple times)"); @@ -208,48 +256,47 @@ void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_opt } sidechain_enabled_bitcoin = options.at("bitcoin-sidechain-enabled").as(); - config_ready_bitcoin = options.count("bitcoin-node-ip") && - options.count("bitcoin-node-zmq-port") && options.count("bitcoin-node-rpc-port") && - options.count("bitcoin-node-rpc-user") && options.count("bitcoin-node-rpc-password") && - /*options.count("bitcoin-wallet") && options.count("bitcoin-wallet-password") &&*/ - options.count("bitcoin-private-key"); + + config_ready_bitcoin = (((options.count("libbitcoin-server-ip") && options.count("libbitcoin-server-zmq-port")) || + (options.count("bitcoin-node-ip") && options.count("bitcoin-node-zmq-port") && + options.count("bitcoin-node-rpc-port") && options.count("bitcoin-node-rpc-user") && + options.count("bitcoin-node-rpc-password") && options.count("bitcoin-wallet-name") && + options.count("bitcoin-wallet-password"))) && + options.count("bitcoin-private-key")); if (!config_ready_bitcoin) { wlog("Haven't set up Bitcoin sidechain parameters"); } - //sidechain_enabled_ethereum = options.at("ethereum-sidechain-enabled").as(); - //config_ready_ethereum = options.count("ethereum-node-ip") && - // options.count("ethereum-address") && - // options.count("ethereum-public-key") && options.count("ethereum-private-key"); - //if (!config_ready_ethereum) { - // wlog("Haven't set up Ethereum sidechain parameters"); - //} + sidechain_enabled_ethereum = options.at("ethereum-sidechain-enabled").as(); + config_ready_ethereum = options.count("ethereum-node-rpc-url") && + /*options.count("ethereum-node-rpc-user") && options.count("ethereum-node-rpc-password") &&*/ + options.count("ethereum-wallet-contract-address") && + options.count("ethereum-private-key"); + if (sidechain_enabled_ethereum && !config_ready_ethereum) { + wlog("Haven't set up Ethereum sidechain parameters"); + } sidechain_enabled_hive = options.at("hive-sidechain-enabled").as(); config_ready_hive = options.count("hive-node-rpc-url") && /*options.count("hive-node-rpc-user") && options.count("hive-node-rpc-password") &&*/ + options.count("hive-wallet-account-name") && options.count("hive-private-key"); - if (!config_ready_hive) { + if (sidechain_enabled_hive && !config_ready_hive) { wlog("Haven't set up Hive sidechain parameters"); } #ifdef ENABLE_PEERPLAYS_ASSET_DEPOSITS - sidechain_enabled_peerplays = true; //options.at("peerplays-sidechain-enabled").as(); + sidechain_enabled_peerplays = true; #else sidechain_enabled_peerplays = false; #endif config_ready_peerplays = true; - if (!config_ready_peerplays) { + if (sidechain_enabled_peerplays && !config_ready_peerplays) { wlog("Haven't set up Peerplays sidechain parameters"); } - if (!(config_ready_bitcoin && - /*config_ready_ethereum &&*/ - config_ready_hive && - config_ready_peerplays)) { - wlog("Haven't set up any sidechain parameters"); - throw; - } + if (options.at("simulate-rpc-connection-reselection").as()) + ilog("### RPC connection reselection will be simulated"); } void peerplays_sidechain_plugin_impl::plugin_startup() { @@ -262,25 +309,29 @@ void peerplays_sidechain_plugin_impl::plugin_startup() { elog("No sons configured! Please add SON IDs and private keys to configuration."); } - net_manager = std::unique_ptr(new sidechain_net_manager(plugin)); + sidechain_net_handler_factory net_handler_factory(plugin); if (sidechain_enabled_bitcoin && config_ready_bitcoin) { - net_manager->create_handler(sidechain_type::bitcoin, options); + sidechain_enabled.at(sidechain_type::bitcoin) = true; + net_handlers.at(sidechain_type::bitcoin) = net_handler_factory.create_handler(sidechain_type::bitcoin, options); ilog("Bitcoin sidechain handler running"); } - //if (sidechain_enabled_ethereum && config_ready_ethereum) { - // net_manager->create_handler(sidechain_type::ethereum, options); - // ilog("Ethereum sidechain handler running"); - //} + if (sidechain_enabled_ethereum && config_ready_ethereum) { + sidechain_enabled.at(sidechain_type::ethereum) = true; + net_handlers.at(sidechain_type::ethereum) = net_handler_factory.create_handler(sidechain_type::ethereum, options); + ilog("Ethereum sidechain handler running"); + } if (sidechain_enabled_hive && config_ready_hive) { - net_manager->create_handler(sidechain_type::hive, options); + sidechain_enabled.at(sidechain_type::hive) = true; + net_handlers.at(sidechain_type::hive) = net_handler_factory.create_handler(sidechain_type::hive, options); ilog("Hive sidechain handler running"); } if (sidechain_enabled_peerplays && config_ready_peerplays) { - net_manager->create_handler(sidechain_type::peerplays, options); + sidechain_enabled.at(sidechain_type::peerplays) = true; + net_handlers.at(sidechain_type::peerplays) = net_handler_factory.create_handler(sidechain_type::peerplays, options); ilog("Peerplays sidechain handler running"); } @@ -296,12 +347,13 @@ std::set &peerplays_sidechain_plugin_impl::get_sons() { return sons; } -const son_id_type peerplays_sidechain_plugin_impl::get_current_son_id() { - return current_son_id; +const son_id_type peerplays_sidechain_plugin_impl::get_current_son_id(sidechain_type sidechain) { + const std::lock_guard lock(current_son_id_mutex); + return current_son_id.at(sidechain); } -const son_object peerplays_sidechain_plugin_impl::get_current_son_object() { - return get_son_object(current_son_id); +const son_object peerplays_sidechain_plugin_impl::get_current_son_object(sidechain_type sidechain) { + return get_son_object(get_current_son_id(sidechain)); } const son_object peerplays_sidechain_plugin_impl::get_son_object(son_id_type son_id) { @@ -312,18 +364,17 @@ const son_object peerplays_sidechain_plugin_impl::get_son_object(son_id_type son return *son_obj; } -bool peerplays_sidechain_plugin_impl::is_active_son(son_id_type son_id) { +bool peerplays_sidechain_plugin_impl::is_active_son(sidechain_type sidechain, son_id_type son_id) { const auto &idx = plugin.database().get_index_type().indices().get(); auto son_obj = idx.find(son_id); if (son_obj == idx.end()) return false; const chain::global_property_object &gpo = plugin.database().get_global_properties(); - vector active_son_ids; - active_son_ids.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + set active_son_ids; + std::transform(gpo.active_sons.at(sidechain).cbegin(), gpo.active_sons.at(sidechain).cend(), std::inserter(active_son_ids, active_son_ids.end()), - [](const son_info &swi) { + [](const son_sidechain_info &swi) { return swi.son_id; }); @@ -338,7 +389,13 @@ bool peerplays_sidechain_plugin_impl::is_son_deregistered(son_id_type son_id) { if (son_obj == idx.end()) return true; - if (son_obj->status == chain::son_status::deregistered) { + bool status_deregistered = true; + for (const auto &status : son_obj->statuses) { + if ((status.second != son_status::deregistered)) + status_deregistered = false; + } + + if (status_deregistered) { return true; } @@ -355,20 +412,32 @@ bool peerplays_sidechain_plugin_impl::is_son_down_op_valid(const chain::operatio const chain::global_property_object &gpo = d.get_global_properties(); const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties(); const auto &idx = d.get_index_type().indices().get(); - son_report_down_operation down_op = op.get(); - auto son_obj = idx.find(down_op.son_id); + const son_report_down_operation down_op = op.get(); + const auto son_obj = idx.find(down_op.son_id); if (son_obj == idx.end()) { return false; } - auto stats = son_obj->statistics(d); - fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; - fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); - int64_t down_threshold = gpo.parameters.son_down_time(); - if (((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && - ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { - return true; + const auto stats = son_obj->statistics(d); + const fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; + const int64_t down_threshold = gpo.parameters.son_down_time(); + + bool status_son_down_op_valid = true; + for (const auto &status : son_obj->statuses) { + if ((status.second != son_status::active) && (status.second != son_status::request_maintenance)) + status_son_down_op_valid = false; } - return false; + if (status_son_down_op_valid) { + for (const auto &active_sidechain_type : active_sidechain_types(d.head_block_time())) { + if (stats.last_active_timestamp.contains(active_sidechain_type)) { + const fc::time_point_sec last_active_ts = ((stats.last_active_timestamp.at(active_sidechain_type) > last_maintenance_time) ? stats.last_active_timestamp.at(active_sidechain_type) : last_maintenance_time); + if (((fc::time_point::now() - last_active_ts) <= fc::seconds(down_threshold))) { + status_son_down_op_valid = false; + } + } + } + } + + return status_son_down_op_valid; } fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(son_id_type son_id) { @@ -400,7 +469,25 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() { chain::database &d = plugin.database(); for (son_id_type son_id : sons) { - if (is_active_son(son_id) || get_son_object(son_id).status == chain::son_status::in_maintenance) { + const auto &son_obj = get_son_object(son_id); + + //! Check that son is in_maintenance + bool status_in_maintenance = false; + for (const auto &status : son_obj.statuses) { + if ((status.second == son_status::in_maintenance)) + status_in_maintenance = true; + } + + //! Check that son is active (at least for one sidechain_type) + bool is_son_active = false; + for (const auto &active_sidechain_type : active_sidechain_types(d.head_block_time())) { + if (sidechain_enabled.at(active_sidechain_type)) { + if (is_active_son(active_sidechain_type, son_id)) + is_son_active = true; + } + } + + if (is_son_active || status_in_maintenance) { ilog("Sending heartbeat for SON ${son}", ("son", son_id)); chain::son_heartbeat_operation op; @@ -426,19 +513,30 @@ void peerplays_sidechain_plugin_impl::heartbeat_loop() { } void peerplays_sidechain_plugin_impl::schedule_son_processing() { - fc::time_point now = fc::time_point::now(); - int64_t time_to_next_son_processing = 500000; + const auto now = std::chrono::steady_clock::now(); + static const int64_t time_to_next_son_processing = 500000; - fc::time_point next_wakeup(now + fc::microseconds(time_to_next_son_processing)); + const auto next_wakeup = now + std::chrono::microseconds(time_to_next_son_processing); - _son_processing_task = fc::schedule([this] { - son_processing(); - }, - next_wakeup, "SON Processing"); + for (const auto &active_sidechain_type : active_sidechain_types(plugin.database().head_block_time())) { + if (_son_processing_task.count(active_sidechain_type) != 0 && _son_processing_task.at(active_sidechain_type).wait_for(std::chrono::seconds{0}) != std::future_status::ready) { + wlog("Son doesn't process in time for sidechain: ${active_sidechain_type}", ("active_sidechain_type", active_sidechain_type)); + _son_processing_task.at(active_sidechain_type).wait(); + } + + _son_processing_task[active_sidechain_type] = std::async(std::launch::async, [this, next_wakeup, active_sidechain_type] { + if (sidechain_enabled.at(active_sidechain_type)) { + std::this_thread::sleep_until(next_wakeup); + son_processing(active_sidechain_type); + } + }); + } } -void peerplays_sidechain_plugin_impl::son_processing() { - if (plugin.database().get_global_properties().active_sons.size() <= 0) { +void peerplays_sidechain_plugin_impl::son_processing(sidechain_type sidechain) { + //! Check whether we have active SONs + if (plugin.database().get_global_properties().active_sons.count(sidechain) == 0 || + plugin.database().get_global_properties().active_sons.at(sidechain).empty()) { return; } @@ -448,50 +546,55 @@ void peerplays_sidechain_plugin_impl::son_processing() { // return; // Not synced //} - fc::time_point now_fine = fc::time_point::now(); - fc::time_point_sec now = now_fine - fc::milliseconds(3000); + const fc::time_point now_fine = fc::time_point::now(); + const fc::time_point_sec now = now_fine - fc::milliseconds(3000); if (plugin.database().head_block_time() < now) { return; // Not synced } - chain::son_id_type scheduled_son_id = plugin.database().get_scheduled_son(1); - ilog("Scheduled SON: ${scheduled_son_id} Now: ${now} ", - ("scheduled_son_id", scheduled_son_id)("now", now)); + //! Get scheduled_son_id according to sidechain_type + const chain::son_id_type scheduled_son_id = plugin.database().get_scheduled_son(sidechain, 1); + ilog("Scheduled SON: ${scheduled_son_id} Sidechain: ${sidechain} Now: ${now}", + ("scheduled_son_id", scheduled_son_id)("sidechain", sidechain)("now", now)); for (son_id_type son_id : plugin.get_sons()) { if (plugin.is_son_deregistered(son_id)) { continue; } - current_son_id = son_id; + + { + const std::lock_guard lock(current_son_id_mutex); + current_son_id.at(sidechain) = son_id; + } // These tasks are executed by // - All active SONs, no matter if scheduled // - All previously active SONs - approve_proposals(); - process_proposals(); - process_sidechain_transactions(); + approve_proposals(sidechain); + process_proposals(sidechain); + process_sidechain_transactions(sidechain); - if (plugin.is_active_son(son_id)) { + if (plugin.is_active_son(sidechain, son_id)) { // Tasks that are executed by scheduled and active SON only - if (current_son_id == scheduled_son_id) { + if (get_current_son_id(sidechain) == scheduled_son_id) { - create_son_down_proposals(); + create_son_down_proposals(sidechain); - create_son_deregister_proposals(); + create_son_deregister_proposals(sidechain); - process_active_sons_change(); + process_active_sons_change(sidechain); - create_deposit_addresses(); + create_deposit_addresses(sidechain); - process_deposits(); + process_deposits(sidechain); - process_withdrawals(); + process_withdrawals(sidechain); - process_sidechain_transactions(); + process_sidechain_transactions(sidechain); - send_sidechain_transactions(); + send_sidechain_transactions(sidechain); - settle_sidechain_transactions(); + settle_sidechain_transactions(sidechain); } } } @@ -514,8 +617,8 @@ bool peerplays_sidechain_plugin_impl::is_valid_son_proposal(const chain::proposa return false; } -void peerplays_sidechain_plugin_impl::log_son_proposal_retry(int op_type, object_id_type object_id) { - son_proposal_type prop_type(op_type, get_current_son_id(), object_id); +void peerplays_sidechain_plugin_impl::log_son_proposal_retry(sidechain_type sidechain, int op_type, object_id_type object_id) { + son_proposal_type prop_type(op_type, sidechain, get_current_son_id(sidechain), object_id); auto itr = son_retry_count.find(prop_type); if (itr != son_retry_count.end()) { itr->second++; @@ -524,18 +627,43 @@ void peerplays_sidechain_plugin_impl::log_son_proposal_retry(int op_type, object } } -bool peerplays_sidechain_plugin_impl::can_son_participate(int op_type, object_id_type object_id) { - son_proposal_type prop_type(op_type, get_current_son_id(), object_id); +bool peerplays_sidechain_plugin_impl::can_son_participate(sidechain_type sidechain, int op_type, object_id_type object_id) { + son_proposal_type prop_type(op_type, sidechain, get_current_son_id(sidechain), object_id); auto itr = son_retry_count.find(prop_type); return (itr == son_retry_count.end() || itr->second < retries_threshold); } std::map> peerplays_sidechain_plugin_impl::get_son_listener_log() { - return net_manager->get_son_listener_log(); + std::map> result; + for (const auto &active_sidechain_type : active_sidechain_types(plugin.database().head_block_time())) { + if (net_handlers.at(active_sidechain_type)) { + result.emplace(active_sidechain_type, net_handlers.at(active_sidechain_type)->get_son_listener_log()); + } + } + return result; } -void peerplays_sidechain_plugin_impl::approve_proposals() { +optional peerplays_sidechain_plugin_impl::estimate_withdrawal_transaction_fee(sidechain_type sidechain) { + if (net_handlers.count(sidechain) == 0) { + wlog("No net handler for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional(); + } + if (!net_handlers.at(sidechain)) { + wlog("Net handler is null for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional(); + } + + return net_handlers.at(sidechain)->estimate_withdrawal_transaction_fee(); +} + +void peerplays_sidechain_plugin_impl::approve_proposals(sidechain_type sidechain) { + // prevent approving duplicate proposals with lock for parallel execution. + // We can have the same propsals, but in the case of parallel execution we can run + // into problem of approving the same propsal since it might happens that previous + // approved proposal didn't have time or chance to populate the list of available + // active proposals which is consulted here in the code. + const std::lock_guard lck{access_approve_prop_mutex}; auto check_approve_proposal = [&](const chain::son_id_type &son_id, const chain::proposal_object &proposal) { if (!is_valid_son_proposal(proposal)) { return; @@ -549,6 +677,7 @@ void peerplays_sidechain_plugin_impl::approve_proposals() { fc::future fut = fc::async([&]() { try { trx.validate(); + std::lock_guard lck(access_db_mutex); plugin.database().push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); @@ -568,7 +697,6 @@ void peerplays_sidechain_plugin_impl::approve_proposals() { } for (const auto proposal_id : proposals) { - const object *obj = plugin.database().find_object(proposal_id); const chain::proposal_object *proposal_ptr = dynamic_cast(obj); if (proposal_ptr == nullptr) { @@ -576,15 +704,16 @@ void peerplays_sidechain_plugin_impl::approve_proposals() { } const proposal_object proposal = *proposal_ptr; - if (proposal.available_active_approvals.find(get_current_son_object().son_account) != proposal.available_active_approvals.end()) { + if (proposal.available_active_approvals.find(get_current_son_object(sidechain).son_account) != proposal.available_active_approvals.end()) { continue; } - check_approve_proposal(get_current_son_id(), proposal); + check_approve_proposal(get_current_son_id(sidechain), proposal); } } -void peerplays_sidechain_plugin_impl::create_son_down_proposals() { +void peerplays_sidechain_plugin_impl::create_son_down_proposals(sidechain_type sidechain) { + const std::lock_guard lck{access_son_down_prop_mutex}; auto create_son_down_proposal = [&](chain::son_id_type son_id, fc::time_point_sec last_active_ts) { chain::database &d = plugin.database(); const chain::global_property_object &gpo = d.get_global_properties(); @@ -595,9 +724,9 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals() { son_down_op.down_ts = last_active_ts; proposal_create_operation proposal_op; - proposal_op.fee_paying_account = get_current_son_object().son_account; + proposal_op.fee_paying_account = get_current_son_object(sidechain).son_account; proposal_op.proposed_ops.emplace_back(op_wrapper(son_down_op)); - uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + const uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(d.head_block_time().sec_since_epoch() + lifetime); return proposal_op; }; @@ -606,25 +735,41 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals() { const chain::global_property_object &gpo = d.get_global_properties(); const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties(); const auto &idx = d.get_index_type().indices().get(); - std::set sons_being_reported_down = d.get_sons_being_reported_down(); - chain::son_id_type my_son_id = get_current_son_id(); - for (auto son_inf : gpo.active_sons) { + const std::set sons_being_reported_down = d.get_sons_being_reported_down(); + const chain::son_id_type my_son_id = get_current_son_id(sidechain); + + //! Fixme - check this part of the code + for (auto son_inf : gpo.active_sons.at(sidechain)) { if (my_son_id == son_inf.son_id || (sons_being_reported_down.find(son_inf.son_id) != sons_being_reported_down.end())) { continue; } - auto son_obj = idx.find(son_inf.son_id); - auto stats = son_obj->statistics(d); - fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; - fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); - int64_t down_threshold = gpo.parameters.son_down_time(); - if (((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && - ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { + + const auto son_obj = idx.find(son_inf.son_id); + const auto stats = son_obj->statistics(d); + const fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; + const fc::time_point_sec last_active_ts = [&stats, &sidechain, &last_maintenance_time] { + fc::time_point_sec last_active_ts; + if (stats.last_active_timestamp.contains(sidechain)) { + last_active_ts = (stats.last_active_timestamp.at(sidechain) > last_maintenance_time) ? stats.last_active_timestamp.at(sidechain) : last_maintenance_time; + } else + last_active_ts = last_maintenance_time; + return last_active_ts; + }(); + const int64_t down_threshold = gpo.parameters.son_down_time(); + + bool status_son_down_valid = true; + for (const auto &status : son_obj->statuses) { + if ((status.second != son_status::active) && (status.second != son_status::request_maintenance)) + status_son_down_valid = false; + } + if ((status_son_down_valid) && ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { ilog("Sending son down proposal for ${t} from ${s}", ("t", std::string(object_id_type(son_obj->id)))("s", std::string(object_id_type(my_son_id)))); chain::proposal_create_operation op = create_son_down_proposal(son_inf.son_id, last_active_ts); chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), op); fc::future fut = fc::async([&]() { try { trx.validate(); + std::lock_guard lck(access_db_mutex); d.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); @@ -639,10 +784,11 @@ void peerplays_sidechain_plugin_impl::create_son_down_proposals() { } } -void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() { +void peerplays_sidechain_plugin_impl::create_son_deregister_proposals(sidechain_type sidechain) { + const std::lock_guard lck{access_son_deregister_prop_mutex}; chain::database &d = plugin.database(); std::set sons_to_be_dereg = d.get_sons_to_be_deregistered(); - chain::son_id_type my_son_id = get_current_son_id(); + chain::son_id_type my_son_id = get_current_son_id(sidechain); if (sons_to_be_dereg.size() > 0) { // We shouldn't raise proposals for the SONs for which a de-reg @@ -660,6 +806,7 @@ void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() { fc::future fut = fc::async([&]() { try { trx.validate(); + std::lock_guard lck(access_db_mutex); d.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); @@ -676,41 +823,69 @@ void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() { } } -void peerplays_sidechain_plugin_impl::process_proposals() { - net_manager->process_proposals(); +void peerplays_sidechain_plugin_impl::process_proposals(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->process_proposals(); + } } -void peerplays_sidechain_plugin_impl::process_active_sons_change() { - net_manager->process_active_sons_change(); +void peerplays_sidechain_plugin_impl::process_active_sons_change(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->process_active_sons_change(); + } } -void peerplays_sidechain_plugin_impl::create_deposit_addresses() { - net_manager->create_deposit_addresses(); +void peerplays_sidechain_plugin_impl::create_deposit_addresses(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->create_deposit_addresses(); + } } -void peerplays_sidechain_plugin_impl::process_deposits() { - net_manager->process_deposits(); +void peerplays_sidechain_plugin_impl::process_deposits(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->process_deposits(); + } } -void peerplays_sidechain_plugin_impl::process_withdrawals() { - net_manager->process_withdrawals(); +void peerplays_sidechain_plugin_impl::process_withdrawals(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->process_withdrawals(); + } } -void peerplays_sidechain_plugin_impl::process_sidechain_transactions() { - net_manager->process_sidechain_transactions(); +void peerplays_sidechain_plugin_impl::process_sidechain_transactions(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->process_sidechain_transactions(); + } } -void peerplays_sidechain_plugin_impl::send_sidechain_transactions() { - net_manager->send_sidechain_transactions(); +void peerplays_sidechain_plugin_impl::send_sidechain_transactions(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->send_sidechain_transactions(); + } } -void peerplays_sidechain_plugin_impl::settle_sidechain_transactions() { - net_manager->settle_sidechain_transactions(); +void peerplays_sidechain_plugin_impl::settle_sidechain_transactions(sidechain_type sidechain) { + if (net_handlers.at(sidechain)) { + net_handlers.at(sidechain)->settle_sidechain_transactions(); + } } void peerplays_sidechain_plugin_impl::on_applied_block(const signed_block &b) { if (first_block_skipped) { - schedule_son_processing(); + if(son_processing_enabled) { + schedule_son_processing(); + } + else + { + const fc::time_point now_fine = fc::time_point::now(); + const fc::time_point_sec now = now_fine + fc::microseconds( 500000 ); + if( plugin.database().get_slot_time(1) >= now ) + { + son_processing_enabled = true; + schedule_son_processing(); + } + } } else { first_block_skipped = true; } @@ -758,20 +933,20 @@ std::set &peerplays_sidechain_plugin::get_sons() { return my->get_sons(); } -const son_id_type peerplays_sidechain_plugin::get_current_son_id() { - return my->get_current_son_id(); +const son_id_type peerplays_sidechain_plugin::get_current_son_id(sidechain_type sidechain) { + return my->get_current_son_id(sidechain); } -const son_object peerplays_sidechain_plugin::get_current_son_object() { - return my->get_current_son_object(); +const son_object peerplays_sidechain_plugin::get_current_son_object(sidechain_type sidechain) { + return my->get_current_son_object(sidechain); } const son_object peerplays_sidechain_plugin::get_son_object(son_id_type son_id) { return my->get_son_object(son_id); } -bool peerplays_sidechain_plugin::is_active_son(son_id_type son_id) { - return my->is_active_son(son_id); +bool peerplays_sidechain_plugin::is_active_son(sidechain_type sidechain, son_id_type son_id) { + return my->is_active_son(sidechain, son_id); } bool peerplays_sidechain_plugin::is_son_deregistered(son_id_type son_id) { @@ -786,16 +961,20 @@ fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(chain::public_k return my->get_private_key(public_key); } -void peerplays_sidechain_plugin::log_son_proposal_retry(int op_type, object_id_type object_id) { - my->log_son_proposal_retry(op_type, object_id); +void peerplays_sidechain_plugin::log_son_proposal_retry(sidechain_type sidechain, int op_type, object_id_type object_id) { + my->log_son_proposal_retry(sidechain, op_type, object_id); } -bool peerplays_sidechain_plugin::can_son_participate(int op_type, object_id_type object_id) { - return my->can_son_participate(op_type, object_id); +bool peerplays_sidechain_plugin::can_son_participate(sidechain_type sidechain, int op_type, object_id_type object_id) { + return my->can_son_participate(sidechain, op_type, object_id); } std::map> peerplays_sidechain_plugin::get_son_listener_log() { return my->get_son_listener_log(); } +optional peerplays_sidechain_plugin::estimate_withdrawal_transaction_fee(sidechain_type sidechain) { + return my->estimate_withdrawal_transaction_fee(sidechain); +} + }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_api.cpp b/libraries/plugins/peerplays_sidechain/sidechain_api.cpp index 2a85d034..833d91ff 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_api.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_api.cpp @@ -12,6 +12,7 @@ public: std::shared_ptr get_plugin(); std::map> get_son_listener_log(); + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain); private: app::application &app; @@ -32,6 +33,10 @@ std::map> sidechain_api_impl::get_son_l return get_plugin()->get_son_listener_log(); } +optional sidechain_api_impl::estimate_withdrawal_transaction_fee(sidechain_type sidechain) { + return get_plugin()->estimate_withdrawal_transaction_fee(sidechain); +} + } // namespace detail sidechain_api::sidechain_api(graphene::app::application &_app) : @@ -45,4 +50,8 @@ std::map> sidechain_api::get_son_listen return my->get_son_listener_log(); } +optional sidechain_api::estimate_withdrawal_transaction_fee(sidechain_type sidechain) { + return my->estimate_withdrawal_transaction_fee(sidechain); +} + }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp index 7c87a9ef..667f1edc 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -9,7 +9,8 @@ namespace graphene { namespace peerplays_sidechain { -sidechain_net_handler::sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : +sidechain_net_handler::sidechain_net_handler(sidechain_type _sidechain, peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain(_sidechain), plugin(_plugin), database(_plugin.database()) { @@ -21,11 +22,11 @@ sidechain_net_handler::sidechain_net_handler(peerplays_sidechain_plugin &_plugin sidechain_net_handler::~sidechain_net_handler() { } -sidechain_type sidechain_net_handler::get_sidechain() { +sidechain_type sidechain_net_handler::get_sidechain() const { return sidechain; } -std::vector sidechain_net_handler::get_sidechain_deposit_addresses() { +std::vector sidechain_net_handler::get_sidechain_deposit_addresses() const { std::vector result; const auto &sidechain_addresses_idx = database.get_index_type(); @@ -38,7 +39,7 @@ std::vector sidechain_net_handler::get_sidechain_deposit_addresses( return result; } -std::vector sidechain_net_handler::get_sidechain_withdraw_addresses() { +std::vector sidechain_net_handler::get_sidechain_withdraw_addresses() const { std::vector result; const auto &sidechain_addresses_idx = database.get_index_type(); @@ -51,7 +52,20 @@ std::vector sidechain_net_handler::get_sidechain_withdraw_addresses return result; } -std::string sidechain_net_handler::get_private_key(std::string public_key) { +std::vector sidechain_net_handler::get_sidechain_transaction_objects(sidechain_transaction_status status) const { + std::vector result; + + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, status)); + std::for_each(idx_range.first, idx_range.second, + [&result](const sidechain_transaction_object &sto) { + result.push_back(sto); + }); + + return result; +} + +std::string sidechain_net_handler::get_private_key(std::string public_key) const { auto private_key_itr = private_keys.find(public_key); if (private_key_itr != private_keys.end()) { return private_key_itr->second; @@ -172,26 +186,32 @@ void sidechain_net_handler::sidechain_event_data_received(const sidechain_event_ #ifdef ENABLE_PEERPLAYS_ASSET_DEPOSITS //enable_peerplays_asset_deposits = (sed.sidechain == sidechain_type::peerplays) && // (sed.sidechain_currency.compare("BTC") != 0) && + // (sed.sidechain_currency.compare("ETH") != 0) && // (sed.sidechain_currency.compare("HBD") != 0) && // (sed.sidechain_currency.compare("HIVE") != 0); #endif - bool deposit_condition = (sed.peerplays_to == gpo.parameters.son_account()) && - (((sed.sidechain == sidechain_type::bitcoin) && (sed.sidechain_currency.compare("BTC") == 0)) || - ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency.compare("HBD") == 0)) || - ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency.compare("HIVE") == 0)) || - enable_peerplays_asset_deposits); + const bool deposit_condition = (sed.peerplays_to == gpo.parameters.son_account()) && + (sed.sidechain == sidechain) && + (sed.type == sidechain_event_type::deposit) && + (((sed.sidechain == sidechain_type::bitcoin) && (sed.sidechain_currency.compare("BTC") == 0)) || + ((sed.sidechain == sidechain_type::ethereum) && (!sed.sidechain_currency.empty())) || + ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency.compare("HBD") == 0)) || + ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency.compare("HIVE") == 0)) || + enable_peerplays_asset_deposits); - bool withdraw_condition = (sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain == sidechain_type::peerplays) && - ((sed.sidechain_currency == object_id_to_string(gpo.parameters.btc_asset())) || - (sed.sidechain_currency == object_id_to_string(gpo.parameters.hbd_asset())) || - (sed.sidechain_currency == object_id_to_string(gpo.parameters.hive_asset()))); + const bool withdraw_condition = (sed.peerplays_to == gpo.parameters.son_account()) && + (sed.sidechain == sidechain) && + (sed.type == sidechain_event_type::withdrawal) && + (((sed.sidechain == sidechain_type::bitcoin) && (sed.sidechain_currency == object_id_to_string(gpo.parameters.btc_asset()))) || + ((sed.sidechain == sidechain_type::ethereum) && (!sed.sidechain_currency.empty())) || + ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency == object_id_to_string(gpo.parameters.hbd_asset()))) || + ((sed.sidechain == sidechain_type::hive) && (sed.sidechain_currency == object_id_to_string(gpo.parameters.hive_asset())))); // Deposit request if (deposit_condition) { - for (son_id_type son_id : plugin.get_sons()) { - if (plugin.is_active_son(son_id)) { + if (plugin.is_active_son(sidechain, son_id)) { son_wallet_deposit_create_operation op; op.payer = plugin.get_son_object(son_id).son_account; @@ -240,6 +260,10 @@ void sidechain_net_handler::sidechain_event_data_received(const sidechain_event_ withdraw_currency = "BTC"; withdraw_currency_price = database.get(database.get_global_properties().parameters.btc_asset()).options.core_exchange_rate; } + if (sed.sidechain_currency == object_id_to_string(gpo.parameters.eth_asset())) { + withdraw_currency = "ETH"; + withdraw_currency_price = database.get(database.get_global_properties().parameters.eth_asset()).options.core_exchange_rate; + } if (sed.sidechain_currency == object_id_to_string(gpo.parameters.hbd_asset())) { withdraw_currency = "HBD"; withdraw_currency_price = database.get(database.get_global_properties().parameters.hbd_asset()).options.core_exchange_rate; @@ -249,11 +273,21 @@ void sidechain_net_handler::sidechain_event_data_received(const sidechain_event_ withdraw_currency_price = database.get(database.get_global_properties().parameters.hive_asset()).options.core_exchange_rate; } if (withdraw_currency.empty()) { - return; + //! This is ERC-20 withdrawal + const auto asset_object_id = string_to_object_id(sed.sidechain_currency); + const auto &assets_by_id = database.get_index_type().indices().get(); + const auto asset_itr = assets_by_id.find(asset_object_id); + if (asset_itr == assets_by_id.end()) { + wlog("Could not find asset: ${asset_object_id}", ("asset_object_id", asset_object_id)); + return; + } + + withdraw_currency = asset_itr->symbol; + withdraw_currency_price = asset_itr->options.core_exchange_rate; } for (son_id_type son_id : plugin.get_sons()) { - if (plugin.is_active_son(son_id)) { + if (plugin.is_active_son(sidechain, son_id)) { son_wallet_withdraw_create_operation op; op.payer = plugin.get_son_object(son_id).son_account; @@ -297,7 +331,7 @@ void sidechain_net_handler::process_proposals() { const auto po = idx.find(proposal_id); if (po != idx.end()) { - if (po->available_active_approvals.find(plugin.get_current_son_object().son_account) != po->available_active_approvals.end()) { + if (po->available_active_approvals.find(plugin.get_current_son_object(sidechain).son_account) != po->available_active_approvals.end()) { continue; } @@ -380,12 +414,12 @@ void sidechain_net_handler::process_proposals() { elog("=================================================="); } - if (should_process && (op_idx_0 == chain::operation::tag::value || plugin.can_son_participate(op_idx_0, object_id))) { + if (should_process && (op_idx_0 == chain::operation::tag::value || plugin.can_son_participate(sidechain, op_idx_0, object_id))) { bool should_approve = process_proposal(*po); if (should_approve) { - if (approve_proposal(po->id, plugin.get_current_son_id())) { + if (approve_proposal(po->id, plugin.get_current_son_id(sidechain))) { if (op_idx_0 != chain::operation::tag::value) { - plugin.log_son_proposal_retry(op_idx_0, object_id); + plugin.log_son_proposal_retry(sidechain, op_idx_0, object_id); } } } @@ -399,14 +433,14 @@ void sidechain_net_handler::process_active_sons_change() { } void sidechain_net_handler::create_deposit_addresses() { - if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + if (database.get_global_properties().active_sons.at(sidechain).size() < database.get_chain_properties().immutable_parameters.min_son_count) { return; } process_sidechain_addresses(); } void sidechain_net_handler::process_deposits() { - if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + if (database.get_global_properties().active_sons.at(sidechain).size() < database.get_chain_properties().immutable_parameters.min_son_count) { return; } @@ -414,7 +448,7 @@ void sidechain_net_handler::process_deposits() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_deposit_object &swdo) { - if (swdo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(chain::operation::tag::value, swdo.id)) { + if (swdo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(sidechain, chain::operation::tag::value, swdo.id)) { return; } //Ignore the deposits which are not valid anymore, considered refunds. @@ -436,12 +470,12 @@ void sidechain_net_handler::process_deposits() { wlog("Deposit not processed: ${swdo}", ("swdo", swdo)); return; } - plugin.log_son_proposal_retry(chain::operation::tag::value, swdo.id); + plugin.log_son_proposal_retry(sidechain, chain::operation::tag::value, swdo.id); }); } void sidechain_net_handler::process_withdrawals() { - if (database.get_global_properties().active_sons.size() < database.get_chain_properties().immutable_parameters.min_son_count) { + if (database.get_global_properties().active_sons.at(sidechain).size() < database.get_chain_properties().immutable_parameters.min_son_count) { return; } @@ -449,7 +483,7 @@ void sidechain_net_handler::process_withdrawals() { const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_withdraw_object &swwo) { - if (swwo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(chain::operation::tag::value, swwo.id)) { + if (swwo.id == object_id_type(0, 0, 0) || !plugin.can_son_participate(sidechain, chain::operation::tag::value, swwo.id)) { return; } @@ -461,16 +495,15 @@ void sidechain_net_handler::process_withdrawals() { wlog("Withdraw not processed: ${swwo}", ("swwo", swwo)); return; } - plugin.log_son_proposal_retry(chain::operation::tag::value, swwo.id); + plugin.log_son_proposal_retry(sidechain, chain::operation::tag::value, swwo.id); }); } void sidechain_net_handler::process_sidechain_transactions() { - const auto &idx = database.get_index_type().indices().get(); - const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::valid)); + const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::valid); - std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { - if ((sto.id == object_id_type(0, 0, 0)) || !signer_expected(sto, plugin.get_current_son_id())) { + std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) { + if ((sto.id == object_id_type(0, 0, 0)) || !signer_expected(sto, plugin.get_current_son_id(sidechain))) { return; } @@ -485,13 +518,13 @@ void sidechain_net_handler::process_sidechain_transactions() { const chain::global_property_object &gpo = database.get_global_properties(); sidechain_transaction_sign_operation sts_op; - sts_op.signer = plugin.get_current_son_id(); + sts_op.signer = plugin.get_current_son_id(sidechain); sts_op.payer = gpo.parameters.son_account(); sts_op.sidechain_transaction_id = sto.id; sts_op.signature = processed_sidechain_tx; proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); proposal_op.proposed_ops.emplace_back(sts_op); @@ -500,7 +533,7 @@ void sidechain_net_handler::process_sidechain_transactions() { return; } - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -513,10 +546,9 @@ void sidechain_net_handler::process_sidechain_transactions() { } void sidechain_net_handler::send_sidechain_transactions() { - const auto &idx = database.get_index_type().indices().get(); - const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::complete)); + const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::complete); - std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) { if (sto.id == object_id_type(0, 0, 0)) { return; } @@ -531,11 +563,11 @@ void sidechain_net_handler::send_sidechain_transactions() { } sidechain_transaction_send_operation sts_op; - sts_op.payer = plugin.get_current_son_object().son_account; + sts_op.payer = plugin.get_current_son_object(sidechain).son_account; sts_op.sidechain_transaction_id = sto.id; sts_op.sidechain_transaction = sidechain_transaction; - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), sts_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), sts_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -548,10 +580,9 @@ void sidechain_net_handler::send_sidechain_transactions() { } void sidechain_net_handler::settle_sidechain_transactions() { - const auto &idx = database.get_index_type().indices().get(); - const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, sidechain_transaction_status::sent)); + const auto stos = get_sidechain_transaction_objects(sidechain_transaction_status::sent); - std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + std::for_each(stos.cbegin(), stos.cend(), [&](const sidechain_transaction_object &sto) { if (sto.id == object_id_type(0, 0, 0)) { return; } @@ -560,7 +591,7 @@ void sidechain_net_handler::settle_sidechain_transactions() { return; } - if (!plugin.can_son_participate(chain::operation::tag::value, sto.object_id)) { + if (!plugin.can_son_participate(sidechain, chain::operation::tag::value, sto.object_id)) { return; } @@ -577,7 +608,7 @@ void sidechain_net_handler::settle_sidechain_transactions() { const chain::global_property_object &gpo = database.get_global_properties(); proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -605,13 +636,13 @@ void sidechain_net_handler::settle_sidechain_transactions() { } } - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); - plugin.log_son_proposal_retry(chain::operation::tag::value, sto.object_id); + plugin.log_son_proposal_retry(sidechain, chain::operation::tag::value, sto.object_id); } catch (fc::exception &e) { elog("Sending proposal for sidechain transaction settle operation failed with exception ${e}", ("e", e.what())); } @@ -646,8 +677,10 @@ void sidechain_net_handler::on_applied_block(const signed_block &b) { continue; } - bool is_tracked_asset = + const bool is_tracked_asset = ((sidechain == sidechain_type::bitcoin) && (transfer_op.amount.asset_id == gpo.parameters.btc_asset())) || + ((sidechain == sidechain_type::ethereum) && (transfer_op.amount.asset_id == gpo.parameters.eth_asset())) || + ((sidechain == sidechain_type::ethereum) && (transfer_op.amount.asset_id != gpo.parameters.btc_asset()) && (transfer_op.amount.asset_id != gpo.parameters.hbd_asset()) && (transfer_op.amount.asset_id != gpo.parameters.hive_asset()) && (transfer_op.amount.asset_id != asset_id_type())) || ((sidechain == sidechain_type::hive) && (transfer_op.amount.asset_id == gpo.parameters.hbd_asset())) || ((sidechain == sidechain_type::hive) && (transfer_op.amount.asset_id == gpo.parameters.hive_asset())); @@ -673,7 +706,8 @@ void sidechain_net_handler::on_applied_block(const signed_block &b) { sidechain_event_data sed; sed.timestamp = database.head_block_time(); sed.block_num = database.head_block_num(); - sed.sidechain = sidechain_type::peerplays; + sed.sidechain = sidechain; + sed.type = sidechain_event_type::withdrawal; sed.sidechain_uid = sidechain_uid; sed.sidechain_transaction_id = trx.id().str(); sed.sidechain_from = sidechain_from; diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index a9e59307..974670ea 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -1,7 +1,6 @@ #include #include -#include #include #include @@ -14,7 +13,7 @@ #include #include #include -#include +#include #include #include #include @@ -26,1042 +25,515 @@ namespace graphene { namespace peerplays_sidechain { // ============================================================================= -bitcoin_rpc_client::bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password, bool _debug_rpc_calls) : - ip(_ip), - rpc_port(_rpc), - user(_user), - password(_password), - wallet(_wallet), - wallet_password(_wallet_password), - debug_rpc_calls(_debug_rpc_calls) { - authorization.key = "Authorization"; - authorization.val = "Basic " + fc::base64_encode(user + ":" + password); -} - -std::string bitcoin_rpc_client::addmultisigaddress(const uint32_t nrequired, const std::vector public_keys) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"addmultisigaddress\", " - "\"method\": \"addmultisigaddress\", \"params\": ["); - try { - std::string params = std::to_string(nrequired) + ", ["; - std::string pubkeys = ""; - for (std::string pubkey : public_keys) { - if (!pubkeys.empty()) { - pubkeys = pubkeys + ","; - } - pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); - } - params = params + pubkeys + std::string("]"); - body = body + params + std::string(", null, \"bech32\"] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::combinepsbt(const vector &psbts) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"combinepsbt\", \"method\": " - "\"combinepsbt\", \"params\": [["); - try { - std::string params = ""; - for (std::string psbt : psbts) { - if (!params.empty()) { - params = params + ","; - } - params = params + std::string("\"") + psbt + std::string("\""); - } - body = body + params + std::string("]] }"); - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createmultisig(const uint32_t nrequired, const std::vector public_keys) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createmultisig\", " - "\"method\": \"createmultisig\", \"params\": ["); - try { - std::string params = std::to_string(nrequired) + ", ["; - std::string pubkeys = ""; - for (std::string pubkey : public_keys) { - if (!pubkeys.empty()) { - pubkeys = pubkeys + ","; - } - pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); - } - params = params + pubkeys + std::string("]"); - body = body + params + std::string(", \"p2sh-segwit\" ] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createpsbt(const std::vector &ins, const fc::flat_map outs) { - std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createpsbt\", " - "\"method\": \"createpsbt\", \"params\": ["); - try { - body += "["; - bool first = true; - for (const auto &entry : ins) { - if (!first) - body += ","; - body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; - first = false; - } - body += "],["; - first = true; - for (const auto &entry : outs) { - if (!first) - body += ","; - body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; - first = false; - } - body += std::string("]] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - if (json.find("result") != json.not_found()) { - return json.get("result"); - } - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createrawtransaction(const std::vector &ins, const fc::flat_map outs) { - std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createrawtransaction\", " - "\"method\": \"createrawtransaction\", \"params\": ["); - try { - body += "["; - bool first = true; - for (const auto &entry : ins) { - if (!first) - body += ","; - body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; - first = false; - } - body += "],["; - first = true; - for (const auto &entry : outs) { - if (!first) - body += ","; - body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; - first = false; - } - body += std::string("]] }"); - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - if (json.find("result") != json.not_found()) { - return json.get("result"); - } - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::createwallet(const std::string &wallet_name) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createwallet\", \"method\": " - "\"createwallet\", \"params\": [\"" + - wallet_name + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::decodepsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decodepsbt\", \"method\": " - "\"decodepsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::decoderawtransaction(std::string const &tx_hex) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decoderawtransaction\", \"method\": " - "\"decoderawtransaction\", \"params\": [\"" + - tx_hex + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::encryptwallet(const std::string &passphrase) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"encryptwallet\", \"method\": " - "\"encryptwallet\", \"params\": [\"" + - passphrase + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } +bitcoin_rpc_client::bitcoin_rpc_client(const std::vector &_credentials, bool _debug_rpc_calls, bool _simulate_connection_reselection) : + rpc_client(sidechain_type::bitcoin, _credentials, _debug_rpc_calls, _simulate_connection_reselection) { } uint64_t bitcoin_rpc_client::estimatesmartfee(uint16_t conf_target) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimatesmartfee\", " - "\"method\": \"estimatesmartfee\", \"params\": [" + - std::to_string(conf_target) + std::string("] }")); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + const std::string params = std::string("[") + std::to_string(conf_target) + std::string("]"); + const std::string str = send_post_request("estimatesmartfee", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return 0; + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return 0; + } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.find("result") != json.not_found()) { + auto json_result = json.get_child("result"); + if (json_result.find("feerate") != json_result.not_found()) { + auto feerate_str = json_result.get("feerate"); + feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); + return std::stoll(feerate_str); } + } - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); + return 20000; +} - if (reply.status == 200) { - if (json.find("result") != json.not_found()) { - auto json_result = json.get_child("result"); - if (json_result.find("feerate") != json_result.not_found()) { - auto feerate_str = json_result.get("feerate"); - feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); - return std::stoll(feerate_str); +std::vector bitcoin_rpc_client::getblock(const block_data &block, int32_t verbosity) { + std::string params = std::string("[\"") + block.block_hash + std::string("\",") + std::to_string(verbosity) + std::string("]"); + std::string str = send_post_request("getblock", params, debug_rpc_calls); + std::vector result; + + if (str.empty()) { + return result; + } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + auto json_result = json.get_child_optional("result"); + + for (const auto &tx_child : json_result.get().get_child("tx")) { + const auto &tx = tx_child.second; + + for (const auto &o : tx.get_child("vout")) { + const auto script = o.second.get_child("scriptPubKey"); + std::vector address_list; + + if (script.count("address")) { + address_list.emplace_back(script.get("address")); + } else if (script.count("addresses")) { + for (const auto &addr : script.get_child("addresses")) { + address_list.emplace_back(addr.second.get_value()); } + } else { + continue; + } - if (json_result.find("errors") != json_result.not_found()) { - wlog("Bitcoin RPC call ${function} with body ${body} executed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } + for (auto &address : address_list) { + const auto address_base58 = address; + info_for_vin vin; + vin.out.hash_tx = tx.get_child("txid").get_value(); + string amount = o.second.get_child("value").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + vin.out.amount = std::stoll(amount); + vin.out.n_vout = o.second.get_child("n").get_value(); + vin.address = address_base58; + result.push_back(vin); } } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return 20000; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return 20000; } + + return result; } -std::string bitcoin_rpc_client::finalizepsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"finalizepsbt\", \"method\": " - "\"finalizepsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); +void bitcoin_rpc_client::getnetworkinfo() { + std::string params = std::string("[]"); + std::string str = send_post_request("getnetworkinfo", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } + std::stringstream network_info_ss(str); + boost::property_tree::ptree network_info_json; + boost::property_tree::read_json(network_info_ss, network_info_json); - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + bitcoin_major_version = network_info_json.get("result.version") / 10000; + ilog("Bitcoin major version is: '${version}'", ("version", bitcoin_major_version)); } -std::string bitcoin_rpc_client::getaddressinfo(const std::string &address) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getaddressinfo\", \"method\": " - "\"getaddressinfo\", \"params\": [\"" + - address + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); +btc_tx bitcoin_rpc_client::getrawtransaction(const std::string &txid, const bool verbose) { + std::string params = std::string("[\"") + txid + std::string("\",") + (verbose ? "true" : "false") + std::string("]"); + std::string str = send_post_request("getrawtransaction", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; + btc_tx tx; + + std::stringstream tx_ss(str); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(tx_ss, tx_json); + + if (tx_json.count("error") && tx_json.get_child("error").empty()) { + + std::string tx_txid = tx_json.get("result.txid"); + uint32_t tx_confirmations = tx_json.get("result.confirmations"); + + tx.tx_txid = tx_txid; + tx.tx_confirmations = tx_confirmations; + + for (auto &input : tx_json.get_child("result.vout")) { + btc_txin tx_in; + std::string tx_vout_s = input.second.get("n"); + tx_in.tx_vout = std::stoll(tx_vout_s); + if (bitcoin_major_version > 21) { + std::string address = input.second.get("scriptPubKey.address"); + tx_in.tx_address.emplace_back(address); + } else { + for (auto &address : input.second.get_child("scriptPubKey.addresses")) { + tx_in.tx_address.emplace_back(address.second.data()); + } + } + + std::string tx_amount_s = input.second.get("value"); + tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); + tx_in.tx_amount = std::stoll(tx_amount_s); + + tx.tx_in_list.emplace_back(tx_in); } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; } -} -std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t verbosity) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblock\", \"method\": " - "\"getblock\", \"params\": [\"" + - block_hash + "\", " + std::to_string(verbosity) + "] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::getnetworkinfo() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getnetworkinfo\", \"method\": " - "\"getnetworkinfo\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::getrawtransaction(const std::string &txid, const bool verbose) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getrawtransaction\", \"method\": " - "\"getrawtransaction\", \"params\": ["); - try { - std::string params = "\"" + txid + "\", " + (verbose ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::gettransaction(const std::string &txid, const bool include_watch_only) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"gettransaction\", \"method\": " - "\"gettransaction\", \"params\": ["); - try { - std::string params = "\"" + txid + "\", " + (include_watch_only ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + return tx; } std::string bitcoin_rpc_client::getblockchaininfo() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblockchaininfo\", \"method\": " - "\"getblockchaininfo\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + static const std::string params = std::string("[]"); + const std::string str = send_post_request("getblockchaininfo", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } + std::string result; - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + if (str.length() > 0) { + + std::stringstream ss(str); boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); + if (json.find("result") != json.not_found()) { + auto json_result = json.get_child("result"); + if (json_result.count("chain")) { + result = json_result.get("chain"); + } } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; } -} -void bitcoin_rpc_client::importaddress(const std::string &address_or_script, const std::string &label, const bool rescan, const bool p2sh) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " - "\"method\": \"importaddress\", \"params\": ["); - try { - std::string params = "\"" + address_or_script + "\", " + - "\"" + label + "\", " + - (rescan ? "true" : "false") + ", " + - (p2sh ? "true" : "false"); - body = body + params + "] }"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return; - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - } + return result; } void bitcoin_rpc_client::importmulti(const std::vector &address_or_script_array, const bool rescan) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importmulti\", " - "\"method\": \"importmulti\", \"params\": ["); - try { - std::string argument_1 = "["; - for (const auto ¶m : address_or_script_array) { - argument_1 += "{\"scriptPubKey\": "; - if (param.type == multi_type::address) { - argument_1 += "{\"address\": \"" + param.address_or_script + "\"}, \"label\": \"" + param.label + "\""; - } else if (param.type == multi_type::script) { - argument_1 += "\"" + param.address_or_script + "\", \"internal\": true"; - } else { - FC_THROW("Invalid multi_type."); - } - argument_1 += ", \"timestamp\": " + std::to_string(fc::time_point_sec::from_iso_string("2022-01-01T00:00:00").sec_since_epoch()) + "}"; + std::string params = std::string("["); + std::string argument_1 = "["; + for (const auto ¶m : address_or_script_array) { + argument_1 += "{\"scriptPubKey\": "; + if (param.type == multi_type::address) { + argument_1 += "{\"address\": \"" + param.address_or_script + "\"}, \"label\": \"" + param.label + "\""; + } else if (param.type == multi_type::script) { + argument_1 += "\"" + param.address_or_script + "\", \"internal\": true"; + } else { + FC_THROW("Invalid multi_type."); + } + argument_1 += ", \"timestamp\": " + std::to_string(fc::time_point_sec::from_iso_string("2022-01-01T00:00:00").sec_since_epoch()) + "}"; - //! Note - /* Creation time of the key expressed in UNIX epoch time, + //! Note + /* Creation time of the key expressed in UNIX epoch time, or the string "now" to substitute the current synced blockchain time. The timestamp of the oldest key will determine how far back blockchain rescans need to begin for missing wallet transactions. "now" can be specified to bypass scanning, for keys which are known to never have been used, and 0 can be specified to scan the entire blockchain. Blocks up to 2 hours before the earliest key creation time of all keys being imported by the importmulti call will be scanned.*/ - if (¶m != &address_or_script_array.back()) { - argument_1 += ", "; - } + if (¶m != &address_or_script_array.back()) { + argument_1 += ", "; } - argument_1 += "]"; - - std::string argument_2 = std::string{"{\"rescan\": "} + (rescan ? "true" : "false") + "}"; - body += argument_1 + ", " + argument_2 + "]}"; - - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return; - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); } + argument_1 += "]"; + + std::string argument_2 = std::string{"{\"rescan\": "} + (rescan ? "true" : "false") + "}"; + params += argument_1 + "," + argument_2 + "]"; + + send_post_request("importmulti", params, debug_rpc_calls); } std::vector bitcoin_rpc_client::listunspent(const uint32_t minconf, const uint32_t maxconf) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " - "\"listunspent\", \"params\": [" + - std::to_string(minconf) + "," + std::to_string(maxconf) + "] }"); - std::vector result; - try { - const auto reply = send_post_request(body, debug_rpc_calls); + const std::string params = std::string("[") + std::to_string(minconf) + "," + std::to_string(maxconf) + std::string("]"); + const std::string str = send_post_request("listunspent", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return result; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - if (json.count("result")) { - for (auto &entry : json.get_child("result")) { - btc_txout txo; - txo.txid_ = entry.second.get_child("txid").get_value(); - txo.out_num_ = entry.second.get_child("vout").get_value(); - string amount = entry.second.get_child("amount").get_value(); - amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); - txo.amount_ = std::stoll(amount); - result.push_back(txo); - } - } - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return result; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return result; } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + + return result; } std::vector bitcoin_rpc_client::listunspent_by_address_and_amount(const std::string &address, double minimum_amount, const uint32_t minconf, const uint32_t maxconf) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " - "\"listunspent\", \"params\": [" + - std::to_string(minconf) + "," + std::to_string(maxconf) + ","); - body += std::string("[\""); - body += address; - body += std::string("\"],true,{\"minimumAmount\":"); - body += std::to_string(minimum_amount); - body += std::string("} ] }"); + + std::string params = std::string("[") + std::to_string(minconf) + "," + std::to_string(maxconf) + ","; + params += std::string("[\""); + params += address; + params += std::string("\"],true,{\"minimumAmount\":"); + params += std::to_string(minimum_amount); + params += std::string("} ]"); std::vector result; + const std::string str = send_post_request("listunspent", params, debug_rpc_calls); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return result; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - if (json.count("result")) { - for (auto &entry : json.get_child("result")) { - btc_txout txo; - txo.txid_ = entry.second.get_child("txid").get_value(); - txo.out_num_ = entry.second.get_child("vout").get_value(); - string amount = entry.second.get_child("amount").get_value(); - amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); - txo.amount_ = std::stoll(amount); - result.push_back(txo); - } - } - } else if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return result; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return result; } + + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + string amount = entry.second.get_child("amount").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + txo.amount_ = std::stoll(amount); + result.push_back(txo); + } + } + + return result; } std::string bitcoin_rpc_client::loadwallet(const std::string &filename) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"loadwallet\", \"method\": " - "\"loadwallet\", \"params\": [\"" + - filename + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + const std::string params = std::string("[\"") + filename + std::string("\"]"); + return send_post_request("loadwallet", params, debug_rpc_calls); } std::string bitcoin_rpc_client::sendrawtransaction(const std::string &tx_hex) { - const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"sendrawtransaction\", " - "\"method\": \"sendrawtransaction\", \"params\": [") + - std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + const std::string params = std::string("[\"") + tx_hex + std::string("\"]"); + const std::string str = send_post_request("sendrawtransaction", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + if (str.length() > 0) { + std::stringstream ss(str); boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - if (reply.status == 200) { - return json.get("result"); - } - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + if (!json.count("error.code")) { + if (json.get_child("error.code").get_value() == -27) { + return tx_hex; + } + } } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; + + return json.get("result"); } -} -std::string bitcoin_rpc_client::signrawtransactionwithwallet(const std::string &tx_hash) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"signrawtransactionwithwallet\", " - "\"method\": \"signrawtransactionwithwallet\", \"params\": ["); - std::string params = "\"" + tx_hash + "\""; - body = body + params + std::string("]}"); - - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } -} - -std::string bitcoin_rpc_client::unloadwallet(const std::string &filename) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"unloadwallet\", \"method\": " - "\"unloadwallet\", \"params\": [\"" + - filename + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + return str; } std::string bitcoin_rpc_client::walletlock() { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " - "\"walletlock\", \"params\": [] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + static const std::string params = std::string("[]"); + const std::string str = send_post_request("walletlock", params, debug_rpc_calls); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - std::stringstream ss; - boost::property_tree::json_parser::write_json(ss, json.get_child("result")); - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); + if (str.length() == 0) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); return ""; } -} -std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletprocesspsbt\", \"method\": " - "\"walletprocesspsbt\", \"params\": [\"" + - tx_psbt + "\"] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); + std::stringstream ss(str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return ""; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return ss.str(); - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } - return ""; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return ""; - } + ss.clear(); + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); } bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { - std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " - "\"walletpassphrase\", \"params\": [\"" + - passphrase + "\", " + std::to_string(timeout) + "] }"); - try { - const auto reply = send_post_request(body, debug_rpc_calls); - - if (reply.body.empty()) { - wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); - return false; - } - - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - boost::property_tree::ptree json; - boost::property_tree::read_json(ss, json); - - if (reply.status == 200) { - return true; - } - - if (json.count("error") && !json.get_child("error").empty()) { - wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); - } + const std::string params = std::string("[\"") + passphrase + std::string("\",") + std::to_string(timeout) + std::string("]"); + const std::string str = send_post_request("walletpassphrase", params, debug_rpc_calls); + if (str.length() == 0) return false; - } catch (const boost::exception &ex) { - wlog("Bitcoin RPC call ${function} with body ${body} generate exception: '${exception}'", ("function", __FUNCTION__)("body", body)("exception", boost::diagnostic_information(ex))); - return false; - } + else + return true; +} +bitcoin_libbitcoin_client::bitcoin_libbitcoin_client(std::string url) : + libbitcoin_client(url) { + + estimate_fee_ext = std::unique_ptr(new estimate_fee_external()); } -fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) { - fc::http::connection conn; - conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); - - std::string url = "http://" + ip + ":" + std::to_string(rpc_port); - - if (wallet.length() > 0) { - url = url + "/wallet/" + wallet; +uint64_t bitcoin_libbitcoin_client::estimatesmartfee(uint16_t conf_target) { + std::vector> fees = estimate_fee_ext->get_fee_external(conf_target); + std::vector accumulated_fees; + for (auto &external_fees : fees) { + if (external_fees.second != 0) { + accumulated_fees.emplace_back(external_fees.second); + } } - fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); - - if (show_log) { - ilog("### Request URL: ${url}", ("url", url)); - ilog("### Request: ${body}", ("body", body)); - std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); - ilog("### Response status: ${status}", ("status", reply.status)); - ilog("### Response: ${ss}", ("ss", ss.str())); + // we will rather pick minimal fee from external sources than internal fee calculation + if (accumulated_fees.empty()) { + accumulated_fees.emplace_back(current_internal_fee); } - return reply; + return *std::min_element(accumulated_fees.begin(), accumulated_fees.end()); +} + +std::vector bitcoin_libbitcoin_client::getblock(const block_data &block, int32_t verbosity) { + + std::unique_lock lck(libbitcoin_event_mutex); + + // estimate fee + const auto &block_trxs = block.block.transactions(); + std::vector bucket_trxs; + for (auto &mem_pool_trx : trx_memory_pool) { + for (auto &trx : block_trxs) { + if (mem_pool_trx.hash() == trx.hash()) { + bucket_trxs.emplace_back(mem_pool_trx); + break; + } + } + } + + uint64_t average_fee = get_average_fee_from_trxs(bucket_trxs); + if (average_fee > 0 && bucket_trxs.size() >= MIN_TRXS_IN_BUCKET) { + current_internal_fee = average_fee; + } + + // We could consider accumulation which could spread to multiple blocks for better metric + // for now we only keep tracking for not confirmed transaction until we get next block + trx_memory_pool.clear(); + + std::vector result; + + const libbitcoin::chain::transaction::list trx_list = block.block.transactions(); + + for (const auto &tx : trx_list) { + uint32_t vout_seq = 0; + for (const auto &o : tx.outputs()) { + std::vector address_list; + + libbitcoin::wallet::payment_address::list addresses; + if (is_test_net) { + addresses = o.addresses(libbitcoin::wallet::payment_address::testnet_p2kh, + libbitcoin::wallet::payment_address::testnet_p2sh); + } else { + addresses = o.addresses(); + } + + for (auto &payment_address : addresses) { + std::stringstream ss; + ss << payment_address; + address_list.emplace_back(ss.str()); + } + + // addres list consists usual of one element + for (auto &address : address_list) { + const auto address_base58 = address; + info_for_vin vin; + vin.out.hash_tx = libbitcoin::config::hash256(tx.hash()).to_string(); + vin.out.amount = std::floor(o.value()); + vin.out.n_vout = vout_seq; + vin.address = address_base58; + result.push_back(vin); + } + vout_seq++; + } + } + + return result; +} + +btc_tx bitcoin_libbitcoin_client::getrawtransaction(const std::string &txid, const bool verbose) { + btc_tx tx; + + std::string tx_hash; + uint32_t confirmitions; + + libbitcoin::chain::output::list outs = get_transaction(txid, tx_hash, confirmitions); + + if (tx_hash.empty()) { + return tx; + } + + tx.tx_txid = tx_hash; + tx.tx_confirmations = confirmitions; + + uint64_t tx_vout_sequence = 0; + + for (auto &out : outs) { + btc_txin tx_in; + tx_in.tx_vout = tx_vout_sequence++; + + libbitcoin::wallet::payment_address::list addresses; + if (is_test_net) { + addresses = out.addresses(libbitcoin::wallet::payment_address::testnet_p2kh, + libbitcoin::wallet::payment_address::testnet_p2sh); + } else { + addresses = out.addresses(); + } + + for (auto &address : addresses) { + + std::stringstream ss; + ss << address; + tx_in.tx_address.emplace_back(ss.str()); + } + + tx_in.tx_amount = std::floor(out.value()); + tx.tx_in_list.emplace_back(tx_in); + } + + return tx; +} + +void bitcoin_libbitcoin_client::getnetworkinfo() { + // This function is only used for bitcoind client in order of getting + // version of bitcoin client. Version is used for extracting addresses or address + // which is not important for libbitcoin client +} + +std::string bitcoin_libbitcoin_client::getblockchaininfo() { + if (get_is_test_net()) { + is_test_net = true; + return "regtest"; + } + + return ""; +} + +std::vector bitcoin_libbitcoin_client::listunspent_by_address_and_amount(const std::string &address, double transfer_amount, const uint32_t minconf, const uint32_t maxconf) { + + std::vector result; + std::vector outputs = listunspent(address, transfer_amount); + for (auto &output : outputs) { + btc_txout txo; + txo.txid_ = output.hash; + txo.out_num_ = output.index; + txo.amount_ = output.value; + result.push_back(txo); + } + + return result; +} + +std::string bitcoin_libbitcoin_client::sendrawtransaction(const std::string &tx_hex) { + std::string res = send_transaction(tx_hex); + return res; +} + +uint64_t bitcoin_rpc_client::ping(rpc_connection &conn) const { + std::string str = send_post_request(conn, "getblockcount", "[]", debug_rpc_calls); + if (str.length() > 0) + return std::stoll(str); + return std::numeric_limits::max(); } // ============================================================================= -zmq_listener::zmq_listener(std::string _ip, uint32_t _zmq) : - ip(_ip), - zmq_port(_zmq), +zmq_listener::zmq_listener(std::string _ip, uint32_t _zmq_block_port, uint32_t _zmq_trx_port) : + zmq_listener_base(_ip, _zmq_block_port, _zmq_trx_port), ctx(1), socket(ctx, ZMQ_SUB) { - std::thread(&zmq_listener::handle_zmq, this).detach(); +} + +void zmq_listener::start() { + int linger = 0; + auto rc = zmq_setsockopt(socket, ZMQ_SUBSCRIBE, "hashblock", 9); + FC_ASSERT(0 == rc); + rc = zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger)); + FC_ASSERT(0 == rc); + int timeout = 100; // millisec + rc = zmq_setsockopt(socket, ZMQ_RCVTIMEO, &timeout, sizeof(timeout)); + // socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); + // socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); + // socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); + socket.connect("tcp://" + ip + ":" + std::to_string(block_zmq_port)); + + block_thr = std::thread(&zmq_listener::handle_zmq, this); + + ilog("zmq_listener thread started"); +} + +zmq_listener::~zmq_listener() { + stopped = true; + block_thr.join(); } std::vector zmq_listener::receive_multipart() { @@ -1078,46 +550,142 @@ std::vector zmq_listener::receive_multipart() { } void zmq_listener::handle_zmq() { - int linger = 0; - auto rc = zmq_setsockopt(socket, ZMQ_SUBSCRIBE, "hashblock", 9); - FC_ASSERT(0 == rc); - rc = zmq_setsockopt(socket, ZMQ_LINGER, &linger, sizeof(linger)); - FC_ASSERT(0 == rc); - //socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); - //socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); - //socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); - socket.connect("tcp://" + ip + ":" + std::to_string(zmq_port)); - - while (true) { + while (false == stopped) { try { - auto msg = receive_multipart(); - const auto header = std::string(static_cast(msg[0].data()), msg[0].size()); - const auto block_hash = boost::algorithm::hex(std::string(static_cast(msg[1].data()), msg[1].size())); - event_received(block_hash); + std::vector msg; + auto res = zmq::recv_multipart(socket, std::back_inserter(msg)); + if (res.has_value()) { + if (3 != *res) { + elog("zmq::recv_multipart returned: ${res}", ("res", *res)); + throw zmq::error_t(); + } + const auto header = std::string(static_cast(msg[0].data()), msg[0].size()); + const auto block_hash = boost::algorithm::hex(std::string(static_cast(msg[1].data()), msg[1].size())); + block_data event_data; + event_data.block_hash = block_hash; + block_event_received(event_data); + } } catch (zmq::error_t &e) { elog("handle_zmq recv_multipart exception ${str}", ("str", e.what())); } } + + ilog("zmq_listener thread finished"); +} + +// ============================================================================= + +// ============================================================================= + +zmq_listener_libbitcoin::zmq_listener_libbitcoin(std::string _ip, uint32_t _block_zmq_port, uint32_t _trx_zmq_port) : + zmq_listener_base(_ip, _block_zmq_port, _trx_zmq_port), + block_socket(block_context, libbitcoin::protocol::zmq::socket::role::subscriber), + trx_socket(trx_context, libbitcoin::protocol::zmq::socket::role::subscriber) { +} + +zmq_listener_libbitcoin::~zmq_listener_libbitcoin() { + stopped.store(true); + block_thr.join(); + trx_thr.join(); +} + +void zmq_listener_libbitcoin::start() { + std::string endpoint_address = "tcp://" + ip; + + libbitcoin::config::endpoint block_address(endpoint_address, block_zmq_port); + libbitcoin::config::endpoint trx_address(endpoint_address, trx_zmq_port); + + block_socket.connect(block_address); + trx_socket.connect(trx_address); + + block_thr = std::thread(&zmq_listener_libbitcoin::handle_block, this); + trx_thr = std::thread(&zmq_listener_libbitcoin::handle_trx, this); +} + +void zmq_listener_libbitcoin::handle_trx() { + trx_poller.add(trx_socket); + + while (!stopped.load()) { + const auto identifiers = trx_poller.wait(500); + + if (identifiers.contains(trx_socket.id())) { + libbitcoin::protocol::zmq::message message; + + trx_socket.receive(message); + + std::vector data; + for (int i = 0; i < 2; i++) { + data.clear(); + message.dequeue(data); + } + + libbitcoin::chain::transaction trx; + trx.from_data(data, true); + + trx_event_received(trx); + } + } + + ilog("zmq_listener_libbitcoin trx thread finished"); +} + +void zmq_listener_libbitcoin::handle_block() { + block_poller.add(block_socket); + + while (!stopped.load()) { + const auto identifiers = block_poller.wait(500); + + if (identifiers.contains(block_socket.id())) { + libbitcoin::protocol::zmq::message message; + + block_socket.receive(message); + + std::vector data; + for (int i = 0; i < 3; i++) { + data.clear(); + message.dequeue(data); + } + + libbitcoin::chain::block block; + block.from_data(data, true); + + block_data event_data; + event_data.block_hash = libbitcoin::config::hash256(block.hash()).to_string(); + event_data.block = std::move(block); + block_event_received(event_data); + } + } + + ilog("zmq_listener_libbitcoin block thread finished"); } // ============================================================================= sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : - sidechain_net_handler(_plugin, options) { - sidechain = sidechain_type::bitcoin; + sidechain_net_handler(sidechain_type::bitcoin, _plugin, options) { if (options.count("debug-rpc-calls")) { debug_rpc_calls = options.at("debug-rpc-calls").as(); } + bool simulate_connection_reselection = options.at("simulate-rpc-connection-reselection").as(); - ip = options.at("bitcoin-node-ip").as(); - zmq_port = options.at("bitcoin-node-zmq-port").as(); - rpc_port = options.at("bitcoin-node-rpc-port").as(); - rpc_user = options.at("bitcoin-node-rpc-user").as(); - rpc_password = options.at("bitcoin-node-rpc-password").as(); - wallet = ""; - if (options.count("bitcoin-wallet")) { - wallet = options.at("bitcoin-wallet").as(); + std::vector ips = options.at("bitcoin-node-ip").as>(); + bitcoin_node_zmq_port = options.at("bitcoin-node-zmq-port").as(); + uint32_t rpc_port = options.at("bitcoin-node-rpc-port").as(); + std::string rpc_user = options.at("bitcoin-node-rpc-user").as(); + std::string rpc_password = options.at("bitcoin-node-rpc-password").as(); + + if (options.count("use-bitcoind-client")) { + use_bitcoind_client = options.at("use-bitcoind-client").as(); + } + + libbitcoin_server_ip = options.at("libbitcoin-server-ip").as(); + libbitcoin_block_zmq_port = options.at("libbitcoin-server-block-zmq-port").as(); + libbitcoin_trx_zmq_port = options.at("libbitcoin-server-trx-zmq-port").as(); + + wallet_name = ""; + if (options.count("bitcoin-wallet-name")) { + wallet_name = options.at("bitcoin-wallet-name").as(); } wallet_password = ""; if (options.count("bitcoin-wallet-password")) { @@ -1136,47 +704,57 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain } } - fc::http::connection conn; - try { - conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); - } catch (fc::exception &e) { - elog("No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", ip)("port", rpc_port)); - FC_ASSERT(false); + if (use_bitcoind_client) { + + for (size_t i = 0; i < ips.size(); i++) { + std::string ip = ips[i]; + std::string url = ip + ":" + std::to_string(rpc_port); + if (!wallet_name.empty()) { + url = url + "/wallet/" + wallet_name; + } + rpc_credentials creds; + creds.url = url; + creds.user = rpc_user; + creds.password = rpc_password; + _rpc_credentials.push_back(creds); + } + FC_ASSERT(!_rpc_credentials.empty()); + + bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(_rpc_credentials, debug_rpc_calls, simulate_connection_reselection)); + if (!wallet_name.empty()) { + bitcoin_client->loadwallet(wallet_name); + } + + listener = std::unique_ptr(new zmq_listener(ips[0], bitcoin_node_zmq_port)); + } else { + bitcoin_client = std::unique_ptr(new bitcoin_libbitcoin_client(libbitcoin_server_ip)); + + listener = std::unique_ptr(new zmq_listener_libbitcoin(libbitcoin_server_ip, libbitcoin_block_zmq_port, libbitcoin_trx_zmq_port)); } - bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(ip, rpc_port, rpc_user, rpc_password, wallet, wallet_password, debug_rpc_calls)); - if (!wallet.empty()) { - bitcoin_client->loadwallet(wallet); - } + std::string chain_info = bitcoin_client->getblockchaininfo(); - std::string blockchain_info = bitcoin_client->getblockchaininfo(); - std::stringstream bci_ss(std::string(blockchain_info.begin(), blockchain_info.end())); - boost::property_tree::ptree bci_json; - boost::property_tree::read_json(bci_ss, bci_json); using namespace bitcoin; network_type = bitcoin_address::network::mainnet; - if (bci_json.count("chain")) { - std::string chain = bci_json.get("chain"); - if (chain == "test") { - network_type = bitcoin_address::network::testnet; - } else if (chain == "regtest") { - network_type = bitcoin_address::network::regtest; - } + + if (chain_info == "test") { + network_type = bitcoin_address::network::testnet; + } else if (chain_info == "regtest") { + network_type = bitcoin_address::network::regtest; } - std::string network_info_str = bitcoin_client->getnetworkinfo(); - std::stringstream network_info_ss(network_info_str); - boost::property_tree::ptree network_info_json; - boost::property_tree::read_json(network_info_ss, network_info_json); + bitcoin_client->getnetworkinfo(); - bitcoin_major_version = network_info_json.get("result.version") / 10000; - ilog("Bitcoin major version is: '${version}'", ("version", bitcoin_major_version)); - - listener = std::unique_ptr(new zmq_listener(ip, zmq_port)); - listener->event_received.connect([this](const std::string &event_data) { - std::thread(&sidechain_net_handler_bitcoin::handle_event, this, event_data).detach(); + listener->block_event_received.connect([this](const block_data &block_event_data) { + std::thread(&sidechain_net_handler_bitcoin::block_handle_event, this, block_event_data).detach(); }); + listener->trx_event_received.connect([this](const libbitcoin::chain::transaction &trx_event_data) { + std::thread(&sidechain_net_handler_bitcoin::trx_handle_event, this, trx_event_data).detach(); + }); + + listener->start(); + database.changed_objects.connect([this](const vector &ids, const flat_set &accounts) { on_changed_objects(ids, accounts); }); @@ -1188,7 +766,7 @@ sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { on_changed_objects_task.cancel_and_wait(__FUNCTION__); } } catch (fc::canceled_exception &) { - //Expected exception. Move along. + // Expected exception. Move along. } catch (fc::exception &e) { edump((e.to_detail_string())); } @@ -1196,7 +774,7 @@ sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) { - //ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id(sidechain))); bool should_approve = false; @@ -1226,12 +804,15 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) bool transaction_ok = false; std::string new_pw_address = ""; son_wallet_id_type swo_id = op_obj_idx_0.get().son_wallet_id; + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = (swo_id.instance.value - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const son_wallet_id_type op_id{id}; const auto &idx = database.get_index_type().indices().get(); - const auto swo = idx.find(swo_id); + const auto swo = idx.find(op_id); if (swo != idx.end()) { - auto active_sons = gpo.active_sons; - vector wallet_sons = swo->sons; + const auto &active_sons = gpo.active_sons.at(sidechain); + const auto &wallet_sons = swo->sons.at(sidechain); bool son_sets_equal = (active_sons.size() == wallet_sons.size()); @@ -1242,10 +823,10 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) } if (son_sets_equal) { - auto active_sons = gpo.active_sons; + const auto &active_sons = gpo.active_sons.at(sidechain); vector son_pubkeys_bitcoin; - for (const son_info &si : active_sons) { - son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); + for (const auto &si : active_sons) { + son_pubkeys_bitcoin.push_back(si.public_key); } string reply_str = create_primary_wallet_address(active_sons); @@ -1263,7 +844,10 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) } if (po.proposed_transaction.operations.size() >= 2) { - object_id_type object_id = op_obj_idx_1.get().object_id; + const object_id_type object_id = op_obj_idx_1.get().object_id; + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = (object_id.instance() - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const object_id_type obj_id{object_id.space(), object_id.type(), id}; std::string op_tx_str = op_obj_idx_1.get().transaction; const auto &st_idx = database.get_index_type().indices().get(); @@ -1272,9 +856,9 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) std::string tx_str = ""; - if (object_id.is()) { + if (obj_id.is()) { const auto &idx = database.get_index_type().indices().get(); - const auto swo = idx.find(object_id); + const auto swo = idx.find(obj_id); if (swo != idx.end()) { tx_str = create_primary_wallet_transaction(*swo, new_pw_address); } @@ -1305,39 +889,26 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) uint64_t swdo_amount = swdo->sidechain_amount.value; uint64_t swdo_vout = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-") + 1)); - std::string tx_str = bitcoin_client->getrawtransaction(swdo_txid, true); - std::stringstream tx_ss(tx_str); - boost::property_tree::ptree tx_json; - boost::property_tree::read_json(tx_ss, tx_json); + btc_tx tx = bitcoin_client->getrawtransaction(swdo_txid, true); - if (tx_json.count("error") && tx_json.get_child("error").empty()) { + if (!tx.tx_in_list.empty()) { - std::string tx_txid = tx_json.get("result.txid"); - uint32_t tx_confirmations = tx_json.get("result.confirmations"); + std::string tx_txid = tx.tx_txid; + uint32_t tx_confirmations = tx.tx_confirmations; std::string tx_address = ""; uint64_t tx_amount = -1; uint64_t tx_vout = -1; - for (auto &input : tx_json.get_child("result.vout")) { - std::string tx_vout_s = input.second.get("n"); - tx_vout = std::stoll(tx_vout_s); + for (auto &input : tx.tx_in_list) { + tx_vout = input.tx_vout; if (tx_vout == swdo_vout) { - if (bitcoin_major_version > 21) { - std::string address = input.second.get("scriptPubKey.address"); + for (auto &address : input.tx_address) { if (address == swdo_address) { tx_address = address; - } - } else { - for (auto &address : input.second.get_child("scriptPubKey.addresses")) { - if (address.second.data() == swdo_address) { - tx_address = address.second.data(); - break; - } + break; } } - std::string tx_amount_s = input.second.get("value"); - tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); - tx_amount = std::stoll(tx_amount_s); + tx_amount = input.tx_amount; break; } } @@ -1455,7 +1026,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) read_transaction_data(sto->transaction, tx_hex, in_amounts, redeem_script); bitcoin_transaction tx = unpack(parse_hex(tx_hex)); - bitcoin::bytes pubkey = parse_hex(son->sidechain_public_keys.at(sidechain_type::bitcoin)); + bitcoin::bytes pubkey = parse_hex(son->sidechain_public_keys.at(sidechain)); vector sigs = read_byte_arrays_from_string(signature); for (size_t i = 0; i < tx.vin.size(); i++) { const auto &sighash_str = get_signature_hash(tx, parse_hex(redeem_script), static_cast(in_amounts[i]), i, 1, true).str(); @@ -1485,28 +1056,40 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { const auto &active_sw = swi.rbegin(); if (active_sw != swi.rend()) { - if ((active_sw->addresses.find(sidechain_type::bitcoin) == active_sw->addresses.end()) || - (active_sw->addresses.at(sidechain_type::bitcoin).empty())) { + const auto &prev_sw = std::next(active_sw); + if (prev_sw != swi.rend() && active_sw->sons.at(sidechain) == prev_sw->sons.at(sidechain)) + return; - if (proposal_exists(chain::operation::tag::value, active_sw->id)) { + if ((active_sw->addresses.find(sidechain) == active_sw->addresses.end()) || + (active_sw->addresses.at(sidechain).empty())) { + + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = active_sw->id.instance() * ast.size() + std::distance(ast.begin(), ast.find(sidechain)); + const object_id_type op_id{active_sw->id.space(), active_sw->id.type(), id}; + + if (proposal_exists(chain::operation::tag::value, op_id)) { + return; + } + + if (!plugin.can_son_participate(sidechain, chain::operation::tag::value, op_id)) { return; } const chain::global_property_object &gpo = database.get_global_properties(); - auto active_sons = gpo.active_sons; + const auto &active_sons = gpo.active_sons.at(sidechain); string reply_str = create_primary_wallet_address(active_sons); std::stringstream active_pw_ss(reply_str); boost::property_tree::ptree active_pw_pt; boost::property_tree::read_json(active_pw_ss, active_pw_pt); if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) { - if (!plugin.can_son_participate(chain::operation::tag::value, active_sw->id)) { + if (!plugin.can_son_participate(sidechain, chain::operation::tag::value, op_id)) { return; } proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -1515,34 +1098,44 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { son_wallet_update_operation swu_op; swu_op.payer = gpo.parameters.son_account(); - swu_op.son_wallet_id = active_sw->id; - swu_op.sidechain = sidechain_type::bitcoin; + swu_op.son_wallet_id = op_id; + swu_op.sidechain = sidechain; swu_op.address = res.str(); proposal_op.proposed_ops.emplace_back(swu_op); - const auto &prev_sw = std::next(active_sw); if (prev_sw != swi.rend()) { std::string new_pw_address = active_pw_pt.get("result.address"); std::string tx_str = create_primary_wallet_transaction(*prev_sw, new_pw_address); if (!tx_str.empty()) { + const auto ast = active_sidechain_types(database.head_block_time()); + const auto prev_id = prev_sw->id.instance() * ast.size() + std::distance(ast.begin(), ast.find(sidechain)); + const object_id_type prev_op_id{prev_sw->id.space(), prev_sw->id.type(), prev_id}; + sidechain_transaction_create_operation stc_op; stc_op.payer = gpo.parameters.son_account(); - stc_op.object_id = prev_sw->id; + stc_op.object_id = prev_op_id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = prev_sw->sons; + for (const auto &signer : prev_sw->sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_op.proposed_ops.emplace_back(stc_op); } } - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); if (plugin.app().p2p_node()) plugin.app().p2p_node()->broadcast(net::trx_message(trx)); - plugin.log_son_proposal_retry(chain::operation::tag::value, active_sw->id); + plugin.log_son_proposal_retry(sidechain, chain::operation::tag::value, op_id); } catch (fc::exception &e) { elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what())); return; @@ -1557,9 +1150,8 @@ void sidechain_net_handler_bitcoin::process_sidechain_addresses() { const chain::global_property_object &gpo = database.get_global_properties(); std::vector> pubkeys; - for (auto &son : gpo.active_sons) { - std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); - auto pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + for (auto &son : gpo.active_sons.at(sidechain)) { + auto pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(son.public_key))); pubkeys.push_back(std::make_pair(pubkey, son.weight)); } @@ -1573,13 +1165,18 @@ void sidechain_net_handler_bitcoin::process_sidechain_addresses() { if (sao.expires == time_point_sec::maximum()) { auto usr_pubkey = fc::ecc::public_key(create_public_key_data(parse_hex(sao.deposit_public_key))); - btc_one_or_weighted_multisig_address addr(usr_pubkey, pubkeys, network_type); + payment_type payment_type_address = payment_type::P2SH_WSH; + if (use_bitcoind_client) { + payment_type_address = payment_type::P2WSH; + } + + btc_one_or_weighted_multisig_address addr(usr_pubkey, pubkeys, network_type, payment_type_address); std::string address_data = "{ \"redeemScript\": \"" + fc::to_hex(addr.get_redeem_script()) + "\", \"witnessScript\": \"" + fc::to_hex(addr.get_witness_script()) + "\" }"; if (addr.get_address() != sao.deposit_address) { sidechain_address_update_operation op; - op.payer = plugin.get_current_son_object().son_account; + op.payer = plugin.get_current_son_object(sidechain).son_account; op.sidechain_address_id = sao.id; op.sidechain_address_account = sao.sidechain_address_account; op.sidechain = sao.sidechain; @@ -1589,7 +1186,7 @@ void sidechain_net_handler_bitcoin::process_sidechain_addresses() { op.withdraw_public_key = sao.withdraw_public_key; op.withdraw_address = sao.withdraw_address; - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -1621,7 +1218,7 @@ bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_obj const chain::global_property_object &gpo = database.get_global_properties(); proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -1635,10 +1232,17 @@ bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_obj stc_op.object_id = swdo.id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = gpo.active_sons; + for (const auto &signer : gpo.active_sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_op.proposed_ops.emplace_back(stc_op); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -1665,7 +1269,7 @@ bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw const chain::global_property_object &gpo = database.get_global_properties(); proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -1679,10 +1283,17 @@ bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw stc_op.object_id = swwo.id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = gpo.active_sons; + for (const auto &signer : gpo.active_sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_op.proposed_ops.emplace_back(stc_op); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -1716,12 +1327,11 @@ bool sidechain_net_handler_bitcoin::settle_sidechain_transaction(const sidechain return false; } - std::string tx_str = bitcoin_client->getrawtransaction(sto.sidechain_transaction, true); - std::stringstream tx_ss(tx_str); - boost::property_tree::ptree tx_json; - boost::property_tree::read_json(tx_ss, tx_json); + btc_tx tx = bitcoin_client->getrawtransaction(sto.sidechain_transaction, true); - if ((tx_json.count("error")) && (!tx_json.get_child("error").empty())) { + if (tx.tx_in_list.empty()) { + // This case will result with segmentation fault. + // FIXME check if that happened before introducing libbitcoin return false; } @@ -1730,40 +1340,34 @@ bool sidechain_net_handler_bitcoin::settle_sidechain_transaction(const sidechain using namespace bitcoin; std::vector> pubkey_weights; for (auto si : sto.signers) { - std::string pub_key_str = si.sidechain_public_keys.at(sidechain_type::bitcoin); - auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(si.public_key))); pubkey_weights.push_back(std::make_pair(pub_key, si.weight)); } - btc_weighted_multisig_address addr(pubkey_weights, network_type); - std::string tx_txid = tx_json.get("result.txid"); - uint32_t tx_confirmations = tx_json.get("result.confirmations"); + payment_type payment_type_address = payment_type::P2SH_WSH; + if (use_bitcoind_client) { + payment_type_address = payment_type::P2WSH; + } + + btc_weighted_multisig_address addr(pubkey_weights, network_type, payment_type_address); + + std::string tx_txid = tx.tx_txid; + uint32_t tx_confirmations = tx.tx_confirmations; std::string tx_address = addr.get_address(); int64_t tx_amount = -1; if (tx_confirmations >= gpo.parameters.son_bitcoin_min_tx_confirmations()) { if (sto.object_id.is()) { - for (auto &input : tx_json.get_child("result.vout")) { - if (bitcoin_major_version > 21) { - std::string address = input.second.get("scriptPubKey.address"); + for (auto &input : tx.tx_in_list) { + for (auto &address : input.tx_address) { if (address == tx_address) { - std::string tx_amount_s = input.second.get("value"); - tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); - tx_amount = std::stoll(tx_amount_s); - } - } else { - for (auto &address : input.second.get_child("scriptPubKey.addresses")) { - if (address.second.data() == tx_address) { - std::string tx_amount_s = input.second.get("value"); - tx_amount_s.erase(std::remove(tx_amount_s.begin(), tx_amount_s.end(), '.'), tx_amount_s.end()); - tx_amount = std::stoll(tx_amount_s); - break; - } + tx_amount = input.tx_amount; + break; } } + settle_amount = asset(tx_amount, database.get_global_properties().parameters.btc_asset()); + return true; } - settle_amount = asset(tx_amount, database.get_global_properties().parameters.btc_asset()); - return true; } if (sto.object_id.is()) { @@ -1775,17 +1379,25 @@ bool sidechain_net_handler_bitcoin::settle_sidechain_transaction(const sidechain return false; } -std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const std::vector &son_pubkeys) { +optional sidechain_net_handler_bitcoin::estimate_withdrawal_transaction_fee() const { + wlog("estimate_withdrawal_transaction_fee not implemented for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional{}; +} + +std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const std::vector &son_pubkeys) { using namespace bitcoin; std::vector> pubkey_weights; for (auto &son : son_pubkeys) { - std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); - auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(son.public_key))); pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); } - btc_weighted_multisig_address addr(pubkey_weights, network_type); + payment_type payment_type_address = payment_type::P2SH_WSH; + if (use_bitcoind_client) { + payment_type_address = payment_type::P2WSH; + } + btc_weighted_multisig_address addr(pubkey_weights, network_type, payment_type_address); std::stringstream ss; @@ -1798,7 +1410,7 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const s std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(const son_wallet_object &prev_swo, std::string new_sw_address) { - const auto &address_data = prev_swo.addresses.find(sidechain_type::bitcoin); + const auto &address_data = prev_swo.addresses.find(sidechain); if (address_data == prev_swo.addresses.end()) { return ""; } @@ -1820,11 +1432,11 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(con } uint64_t fee_rate = bitcoin_client->estimatesmartfee(); - uint64_t min_fee_rate = 1000; + const uint64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); uint64_t total_amount = 0.0; - std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0); + const std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0); if (inputs.size() == 0) { elog("Failed to find UTXOs to spend for ${pw}", ("pw", prev_pw_address)); @@ -1849,12 +1461,12 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_transaction(con std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_wallet_deposit_object &swdo) { const auto &idx = database.get_index_type().indices().get(); auto obj = idx.rbegin(); - if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + if (obj == idx.rend() || obj->addresses.find(sidechain) == obj->addresses.end()) { return ""; } - //Get redeem script for deposit address + // Get redeem script for deposit address std::string redeem_script = get_redeemscript_for_userdeposit(swdo.sidechain_from); - std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + std::string pw_address_json = obj->addresses.find(sidechain)->second; std::stringstream ss(pw_address_json); boost::property_tree::ptree json; @@ -1867,7 +1479,7 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_ std::string nvout = suid.substr(suid.find_last_of("-") + 1); uint64_t deposit_amount = swdo.sidechain_amount.value; uint64_t fee_rate = bitcoin_client->estimatesmartfee(); - uint64_t min_fee_rate = 1000; + const uint64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); if (fee_rate >= deposit_amount) { @@ -1896,11 +1508,11 @@ std::string sidechain_net_handler_bitcoin::create_deposit_transaction(const son_ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) { const auto &idx = database.get_index_type().indices().get(); auto obj = idx.rbegin(); - if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + if (obj == idx.rend() || obj->addresses.find(sidechain) == obj->addresses.end()) { return ""; } - std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + std::string pw_address_json = obj->addresses.find(sidechain)->second; std::stringstream ss(pw_address_json); boost::property_tree::ptree json; @@ -1910,11 +1522,11 @@ std::string sidechain_net_handler_bitcoin::create_withdrawal_transaction(const s std::string redeem_script = json.get("redeemScript"); int64_t fee_rate = bitcoin_client->estimatesmartfee(); - int64_t min_fee_rate = 1000; + const int64_t min_fee_rate = 1000; fee_rate = std::max(fee_rate, min_fee_rate); int64_t total_amount = 0; - std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0); + const std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0); if (inputs.size() == 0) { elog("Failed to find UTXOs to spend for ${pw}", ("pw", pw_address)); @@ -1965,7 +1577,7 @@ std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector< std::string sidechain_net_handler_bitcoin::sign_transaction(const sidechain_transaction_object &sto) { using namespace bitcoin; - std::string pubkey = plugin.get_current_son_object().sidechain_public_keys.at(sidechain); + std::string pubkey = plugin.get_current_son_object(sidechain).sidechain_public_keys.at(sidechain); std::string prvkey = get_private_key(pubkey); std::vector in_amounts; std::string tx_hex; @@ -2004,8 +1616,8 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran uint32_t inputs_number = in_amounts.size(); vector dummy; dummy.resize(inputs_number); - //Organise weighted address signatures - //Add dummies for empty signatures + // Organise weighted address signatures + // Add dummies for empty signatures vector> signatures; for (unsigned idx = 0; idx < sto.signatures.size(); ++idx) { if (sto.signatures[idx].second.empty()) @@ -2013,28 +1625,42 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran else signatures.push_back(read_byte_arrays_from_string(sto.signatures[idx].second)); } - //Add empty sig for user signature for Deposit transaction + // Add empty sig for user signature for Deposit transaction if (sto.object_id.type() == son_wallet_deposit_object::type_id) { add_signatures_to_transaction_user_weighted_multisig(tx, signatures); } else { add_signatures_to_transaction_weighted_multisig(tx, signatures); } - //Add redeemscripts to vins and make tx ready for sending + // Add redeemscripts to vins and make tx ready for sending sign_witness_transaction_finalize(tx, redeem_scripts, false); + + if (!use_bitcoind_client) { + // get witness script from redeem script + bitcoin::bytes redeem_bytes = parse_hex(redeem_script); + fc::sha256 sha = fc::sha256::hash(&redeem_bytes[0], redeem_bytes.size()); + std::string witness_script(sha.str()); + witness_script.insert(0, std::string("220020")); + for (size_t i = 0; i < tx.vin.size(); i++) { + tx.vin[i].scriptSig = parse_hex(witness_script); + } + } + std::string final_tx_hex = fc::to_hex(pack(tx)); std::string res = bitcoin_client->sendrawtransaction(final_tx_hex); - return res; + if (res.empty()) { + return res; + } + + return tx.get_txid().str(); } -void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { - std::string block = bitcoin_client->getblock(event_data); - if (block.empty()) - return; +void sidechain_net_handler_bitcoin::block_handle_event(const block_data &event_data) { - add_to_son_listener_log("BLOCK : " + event_data); + auto vins = bitcoin_client->getblock(event_data); + + add_to_son_listener_log("BLOCK : " + event_data.block_hash); - auto vins = extract_info_from_block(block); scoped_lock interlock(event_handler_mutex); const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); @@ -2053,6 +1679,7 @@ void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) sed.timestamp = database.head_block_time(); sed.block_num = database.head_block_num(); sed.sidechain = addr_itr->sidechain; + sed.type = sidechain_event_type::deposit; sed.sidechain_uid = sidechain_uid; sed.sidechain_transaction_id = v.out.hash_tx; sed.sidechain_from = v.address; @@ -2070,6 +1697,10 @@ void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) } } +void sidechain_net_handler_bitcoin::trx_handle_event(const libbitcoin::chain::transaction &trx_data) { + bitcoin_client->import_trx_to_memory_pool(trx_data); +} + std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(const std::string &user_address) { using namespace bitcoin; const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); @@ -2080,65 +1711,24 @@ std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(cons const auto &idx = database.get_index_type().indices().get(); auto obj = idx.rbegin(); - if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + if (obj == idx.rend() || obj->addresses.find(sidechain) == obj->addresses.end()) { return ""; } std::vector> pubkey_weights; - for (auto &son : obj->sons) { - std::string pub_key_str = son.sidechain_public_keys.at(sidechain_type::bitcoin); - auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(pub_key_str))); + for (auto &son : obj->sons.at(sidechain)) { + auto pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(son.public_key))); pubkey_weights.push_back(std::make_pair(pub_key, son.weight)); } auto user_pub_key = fc::ecc::public_key(create_public_key_data(parse_hex(addr_itr->deposit_public_key))); - btc_one_or_weighted_multisig_address deposit_addr(user_pub_key, pubkey_weights, network_type); - return fc::to_hex(deposit_addr.get_redeem_script()); -} -std::vector sidechain_net_handler_bitcoin::extract_info_from_block(const std::string &_block) { - std::stringstream ss(_block); - boost::property_tree::ptree block; - boost::property_tree::read_json(ss, block); - - std::vector result; - - for (const auto &tx_child : block.get_child("tx")) { - const auto &tx = tx_child.second; - - for (const auto &o : tx.get_child("vout")) { - const auto script = o.second.get_child("scriptPubKey"); - - if (bitcoin_major_version > 21) { - if (!script.count("address")) - continue; - } else { - if (!script.count("addresses")) - continue; - } - - auto sort_out_vin = [&](std::string address) { - const auto address_base58 = address; - info_for_vin vin; - vin.out.hash_tx = tx.get_child("txid").get_value(); - string amount = o.second.get_child("value").get_value(); - amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); - vin.out.amount = std::stoll(amount); - vin.out.n_vout = o.second.get_child("n").get_value(); - vin.address = address_base58; - result.push_back(vin); - }; - - if (bitcoin_major_version > 21) { - std::string address = script.get("address"); - sort_out_vin(address); - } else { - for (const auto &addr : script.get_child("addresses")) // in which cases there can be more addresses? - sort_out_vin(addr.second.get_value()); - } - } + payment_type payment_type_address = payment_type::P2SH_WSH; + if (use_bitcoind_client) { + payment_type_address = payment_type::P2WSH; } + btc_one_or_weighted_multisig_address deposit_addr(user_pub_key, pubkey_weights, network_type, payment_type_address); - return result; + return fc::to_hex(deposit_addr.get_redeem_script()); } void sidechain_net_handler_bitcoin::on_changed_objects(const vector &ids, const flat_set &accounts) { @@ -2195,4 +1785,5 @@ void sidechain_net_handler_bitcoin::on_changed_objects_cb(const vector + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define SEND_RAW_TRANSACTION 1 + +namespace graphene { namespace peerplays_sidechain { + +ethereum_rpc_client::ethereum_rpc_client(const std::vector &credentials, bool debug_rpc_calls, bool simulate_connection_reselection) : + rpc_client(sidechain_type::ethereum, credentials, debug_rpc_calls, simulate_connection_reselection) { +} + +std::string ethereum_rpc_client::eth_blockNumber() { + const std::string reply_str = send_post_request("eth_blockNumber", "", debug_rpc_calls); + return retrieve_value_from_reply(reply_str, ""); +} + +std::string ethereum_rpc_client::eth_get_block_by_number(std::string block_number, bool full_block) { + const std::string params = "[ \"" + block_number + "\", " + (full_block ? "true" : "false") + "]"; + return send_post_request("eth_getBlockByNumber", params, debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_get_logs(std::string wallet_contract_address) { + const std::string params = "[{\"address\": \"" + wallet_contract_address + "\"}]"; + const std::string reply_str = send_post_request("eth_getLogs", params, debug_rpc_calls); + return retrieve_value_from_reply(reply_str, ""); +} + +std::string ethereum_rpc_client::eth_chainId() { + return send_post_request("eth_chainId", "", debug_rpc_calls); +} + +std::string ethereum_rpc_client::net_version() { + return send_post_request("net_version", "", debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_get_transaction_count(const std::string ¶ms) { + return send_post_request("eth_getTransactionCount", params, debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_gas_price() { + return send_post_request("eth_gasPrice", "", debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_estimateGas(const std::string ¶ms) { + return send_post_request("eth_estimateGas", params, debug_rpc_calls); +} + +std::string ethereum_rpc_client::get_chain_id() { + const std::string reply_str = eth_chainId(); + const auto chain_id_string = retrieve_value_from_reply(reply_str, ""); + return chain_id_string.empty() ? "" : std::to_string(ethereum::from_hex(chain_id_string)); +} + +std::string ethereum_rpc_client::get_network_id() { + const std::string reply_str = net_version(); + return retrieve_value_from_reply(reply_str, ""); +} + +std::string ethereum_rpc_client::get_nonce(const std::string &address) { + const std::string reply_str = eth_get_transaction_count("[\"" + address + "\", \"pending\"]"); + const auto nonce_string = retrieve_value_from_reply(reply_str, ""); + if (!nonce_string.empty()) { + const auto nonce_val = ethereum::from_hex(nonce_string); + return nonce_val == 0 ? ethereum::add_0x("0") : ethereum::add_0x(ethereum::to_hex(nonce_val)); + } + return ""; +} + +std::string ethereum_rpc_client::get_gas_price() { + const std::string reply_str = eth_gas_price(); + return retrieve_value_from_reply(reply_str, ""); +} + +std::string ethereum_rpc_client::get_gas_limit() { + const std::string reply_str = eth_get_block_by_number("latest", false); + if (!reply_str.empty()) { + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.count("result")) { + std::string gas_limit_s = json.get("result.gasLimit"); + return gas_limit_s; + } + } + return std::string{}; +} + +std::string ethereum_rpc_client::get_estimate_gas(const std::string ¶ms) { + const std::string reply_str = eth_estimateGas(params); + return retrieve_value_from_reply(reply_str, ""); +} + +std::string ethereum_rpc_client::eth_send_transaction(const std::string ¶ms) { + return send_post_request("eth_sendTransaction", "[" + params + "]", debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_send_raw_transaction(const std::string ¶ms) { + return send_post_request("eth_sendRawTransaction", "[ \"" + params + "\" ]", debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_get_transaction_receipt(const std::string ¶ms) { + return send_post_request("eth_getTransactionReceipt", "[\"" + params + "\"]", debug_rpc_calls); +} + +std::string ethereum_rpc_client::eth_get_transaction_by_hash(const std::string ¶ms) { + return send_post_request("eth_getTransactionByHash", "[\"" + params + "\"]", debug_rpc_calls); +} + +uint64_t ethereum_rpc_client::ping(rpc_connection &conn) const { + std::string reply = send_post_request(conn, "eth_blockNumber", "", debug_rpc_calls); + if (!reply.empty()) + return ethereum::from_hex(retrieve_value_from_reply(reply, "")); + return std::numeric_limits::max(); +} + +sidechain_net_handler_ethereum::sidechain_net_handler_ethereum(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain_net_handler(sidechain_type::ethereum, _plugin, options) { + + if (options.count("debug-rpc-calls")) { + debug_rpc_calls = options.at("debug-rpc-calls").as(); + } + bool simulate_connection_reselection = options.at("simulate-rpc-connection-reselection").as(); + + std::vector rpc_urls = options.at("ethereum-node-rpc-url").as>(); + std::string rpc_user; + if (options.count("ethereum-node-rpc-user")) { + rpc_user = options.at("ethereum-node-rpc-user").as(); + } else { + rpc_user = ""; + } + std::string rpc_password; + if (options.count("ethereum-node-rpc-password")) { + rpc_password = options.at("ethereum-node-rpc-password").as(); + } else { + rpc_password = ""; + } + + wallet_contract_address = options.at("ethereum-wallet-contract-address").as(); + + if (options.count("ethereum-erc-20-address")) { + const std::vector symbol_addresses = options["ethereum-erc-20-address"].as>(); + for (const std::string &itr : symbol_addresses) { + auto itr_pair = graphene::app::dejsonify>(itr, 5); + ilog("ERC-20 symbol: ${symbol}, address: ${address}", ("symbol", itr_pair.first)("address", itr_pair.second)); + if (!itr_pair.first.length() || !itr_pair.second.length()) { + FC_THROW("Invalid symbol address pair."); + } + + auto address = itr_pair.second; + std::transform(address.begin(), address.end(), address.begin(), ::tolower); + erc20_addresses.insert(bimap_type::value_type{itr_pair.first, address}); + } + } + + if (options.count("ethereum-private-key")) { + const std::vector pub_priv_keys = options["ethereum-private-key"].as>(); + for (const std::string &itr_key_pair : pub_priv_keys) { + auto key_pair = graphene::app::dejsonify>(itr_key_pair, 5); + ilog("Ethereum Public Key: ${public}", ("public", key_pair.first)); + if (!key_pair.first.length() || !key_pair.second.length()) { + FC_THROW("Invalid public private key pair."); + } + private_keys[key_pair.first] = key_pair.second; + } + } + + for (size_t i = 0; i < rpc_urls.size(); i++) { + rpc_credentials creds; + creds.url = rpc_urls[i]; + creds.user = rpc_user; + creds.password = rpc_password; + _rpc_credentials.push_back(creds); + } + FC_ASSERT(!_rpc_credentials.empty()); + + rpc_client = new ethereum_rpc_client(_rpc_credentials, debug_rpc_calls, simulate_connection_reselection); + + const std::string chain_id_str = rpc_client->get_chain_id(); + if (chain_id_str.empty()) { + elog("No Ethereum node running at ${url}", ("url", _rpc_credentials[0].url)); + FC_ASSERT(false); + } + chain_id = std::stoll(chain_id_str); + + const std::string network_id_str = rpc_client->get_network_id(); + if (network_id_str.empty()) { + elog("No Ethereum node running at ${url}", ("url", _rpc_credentials[0].url)); + FC_ASSERT(false); + } + network_id = std::stoll(network_id_str); + + ilog("Running on Ethereum network, chain id ${chain_id_str}, network id ${network_id_str}", ("chain_id_str", chain_id_str)("network_id_str", network_id_str)); + + const auto block_number = rpc_client->eth_blockNumber(); + last_block_received = !block_number.empty() ? ethereum::from_hex(block_number) : 0; + schedule_ethereum_listener(); + event_received.connect([this](const std::string &event_data) { + std::thread(&sidechain_net_handler_ethereum::handle_event, this, event_data).detach(); + }); +} + +sidechain_net_handler_ethereum::~sidechain_net_handler_ethereum() { +} + +bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po) { + + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id(sidechain))); + + bool should_approve = false; + + const chain::global_property_object &gpo = database.get_global_properties(); + + int32_t op_idx_0 = -1; + chain::operation op_obj_idx_0; + + if (po.proposed_transaction.operations.size() >= 1) { + op_idx_0 = po.proposed_transaction.operations[0].which(); + op_obj_idx_0 = po.proposed_transaction.operations[0]; + } + + int32_t op_idx_1 = -1; + chain::operation op_obj_idx_1; + (void)op_idx_1; + + if (po.proposed_transaction.operations.size() >= 2) { + op_idx_1 = po.proposed_transaction.operations[1].which(); + op_obj_idx_1 = po.proposed_transaction.operations[1]; + } + + switch (op_idx_0) { + + case chain::operation::tag::value: { + bool address_ok = false; + bool transaction_ok = false; + const son_wallet_id_type swo_id = op_obj_idx_0.get().son_wallet_id; + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = (swo_id.instance.value - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const son_wallet_id_type op_id{id}; + const auto &idx = database.get_index_type().indices().get(); + const auto swo = idx.find(op_id); + if (swo != idx.end()) { + + const auto active_sons = gpo.active_sons.at(sidechain); + const vector wallet_sons = swo->sons.at(sidechain); + + bool son_sets_equal = (active_sons.size() == wallet_sons.size()); + + if (son_sets_equal) { + for (size_t i = 0; i < active_sons.size(); i++) { + son_sets_equal = son_sets_equal && active_sons.at(i) == wallet_sons.at(i); + } + } + + if (son_sets_equal) { + address_ok = (op_obj_idx_0.get().address == wallet_contract_address); + } + + if (po.proposed_transaction.operations.size() >= 2) { + const object_id_type object_id = op_obj_idx_1.get().object_id; + const auto id = (object_id.instance() - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const object_id_type obj_id{object_id.space(), object_id.type(), id}; + const std::string op_tx_str = op_obj_idx_1.get().transaction; + + const auto &st_idx = database.get_index_type().indices().get(); + const auto st = st_idx.find(object_id); + if (st == st_idx.end()) { + + std::string tx_str = ""; + + if (obj_id.is()) { + const auto &idx = database.get_index_type().indices().get(); + const auto swo = idx.find(obj_id); + if (swo != idx.end()) { + tx_str = create_primary_wallet_transaction(gpo.active_sons.at(sidechain), object_id.operator std::string()); + } + } + + transaction_ok = (op_tx_str == tx_str); + } + } else { + transaction_ok = true; + } + } + + should_approve = address_ok && + transaction_ok; + break; + } + + case chain::operation::tag::value: { + bool process_ok = false; + const son_wallet_deposit_id_type swdo_id = op_obj_idx_0.get().son_wallet_deposit_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swdo = idx.find(swdo_id); + if (swdo != idx.end()) { + + const std::string swdo_txid = swdo->sidechain_transaction_id; + const std::string swdo_sidechain_from = swdo->sidechain_from; + const std::string swdo_sidechain_currency = swdo->sidechain_currency; + const uint64_t swdo_sidechain_amount = swdo->sidechain_amount.value; + + const std::string tx_str = rpc_client->eth_get_transaction_by_hash(swdo_txid); + if (tx_str != "") { + + std::stringstream ss_tx(tx_str); + boost::property_tree::ptree tx; + boost::property_tree::read_json(ss_tx, tx); + + if (tx.get("result") != "null") { + + const std::string sidechain_from = tx.get("result.from"); + const std::string sidechain_to = tx.get("result.to"); + + std::string cmp_sidechain_to = sidechain_to; + std::transform(cmp_sidechain_to.begin(), cmp_sidechain_to.end(), cmp_sidechain_to.begin(), ::toupper); + std::string cmp_wallet_contract_address = wallet_contract_address; + std::transform(cmp_wallet_contract_address.begin(), cmp_wallet_contract_address.end(), cmp_wallet_contract_address.begin(), ::toupper); + + //! Check whether it is ERC-20 token deposit + std::string symbol; + boost::multiprecision::uint256_t amount; + bool error_in_deposit = false; + const auto deposit_erc_20 = ethereum::deposit_erc20_decoder::decode(tx.get("result.input")); + if (deposit_erc_20.valid()) { + std::string cmp_token = deposit_erc_20->token; + std::transform(cmp_token.begin(), cmp_token.end(), cmp_token.begin(), ::tolower); + + const auto it = erc20_addresses.right.find(cmp_token); + if (it == erc20_addresses.right.end()) { + wlog("No erc-20 token with address: ${address}", ("address", cmp_token)); + error_in_deposit = true; + } + symbol = it->second; + amount = deposit_erc_20->amount; + } else { + symbol = "ETH"; + const std::string value_s = tx.get("result.value"); + amount = boost::multiprecision::uint256_t{value_s}; + amount = amount / 100000; + amount = amount / 100000; + } + + process_ok = (!error_in_deposit) && + (swdo_sidechain_from == sidechain_from) && + (cmp_sidechain_to == cmp_wallet_contract_address) && + (swdo_sidechain_currency == symbol) && + (swdo_sidechain_amount == fc::safe{amount}.value); + } + } + } + + should_approve = process_ok; + break; + } + + case chain::operation::tag::value: { + bool process_ok = false; + bool transaction_ok = false; + const son_wallet_withdraw_id_type swwo_id = op_obj_idx_0.get().son_wallet_withdraw_id; + const auto &idx = database.get_index_type().indices().get(); + const auto swwo = idx.find(swwo_id); + if (swwo != idx.end()) { + const uint32_t swwo_block_num = swwo->block_num; + const std::string swwo_peerplays_transaction_id = swwo->peerplays_transaction_id; + const uint32_t swwo_op_idx = std::stoll(swwo->peerplays_uid.substr(swwo->peerplays_uid.find_last_of("-") + 1)); + + const auto &block = database.fetch_block_by_number(swwo_block_num); + + for (const auto &tx : block->transactions) { + if (tx.id().str() == swwo_peerplays_transaction_id) { + const operation op = tx.operations[swwo_op_idx]; + const transfer_operation t_op = op.get(); + + const price asset_price = database.get(t_op.amount.asset_id).options.core_exchange_rate; + const asset peerplays_asset = asset(t_op.amount.amount * asset_price.base.amount / asset_price.quote.amount); + + process_ok = (t_op.to == gpo.parameters.son_account()) && + (swwo->peerplays_from == t_op.from) && + (swwo->peerplays_asset == peerplays_asset); + break; + } + } + + const object_id_type object_id = op_obj_idx_1.get().object_id; + const std::string op_tx_str = op_obj_idx_1.get().transaction; + + const auto &st_idx = database.get_index_type().indices().get(); + const auto st = st_idx.find(object_id); + if (st == st_idx.end()) { + + std::string tx_str = ""; + + if (object_id.is()) { + const auto &idx = database.get_index_type().indices().get(); + const auto swwo = idx.find(object_id); + if (swwo != idx.end()) { + tx_str = create_withdrawal_transaction(*swwo); + } + } + + transaction_ok = (op_tx_str == tx_str); + } + } + + should_approve = process_ok && + transaction_ok; + break; + } + + case chain::operation::tag::value: { + should_approve = true; + const son_id_type signer = op_obj_idx_0.get().signer; + const std::string signature = op_obj_idx_0.get().signature; + const sidechain_transaction_id_type sidechain_transaction_id = op_obj_idx_0.get().sidechain_transaction_id; + const auto &st_idx = database.get_index_type().indices().get(); + const auto sto = st_idx.find(sidechain_transaction_id); + if (sto == st_idx.end()) { + should_approve = false; + break; + } + + const auto &s_idx = database.get_index_type().indices().get(); + const auto son = s_idx.find(signer); + if (son == s_idx.end()) { + should_approve = false; + break; + } + + break; + } + + case chain::operation::tag::value: { + should_approve = true; + break; + } + + default: + should_approve = false; + elog("=================================================="); + elog("Proposal not considered for approval ${po}", ("po", po)); + elog("=================================================="); + } + + return should_approve; +} + +void sidechain_net_handler_ethereum::process_primary_wallet() { + const auto &swi = database.get_index_type().indices().get(); + const auto &active_sw = swi.rbegin(); + if (active_sw != swi.rend()) { + + const auto &prev_sw = std::next(active_sw); + if (prev_sw != swi.rend() && active_sw->sons.at(sidechain) == prev_sw->sons.at(sidechain)) + return; + + if ((active_sw->addresses.find(sidechain) == active_sw->addresses.end()) || + (active_sw->addresses.at(sidechain).empty())) { + + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = active_sw->id.instance() * ast.size() + std::distance(ast.begin(), ast.find(sidechain)); + const object_id_type op_id{active_sw->id.space(), active_sw->id.type(), id}; + + if (proposal_exists(chain::operation::tag::value, op_id)) { + return; + } + + if (!plugin.can_son_participate(sidechain, chain::operation::tag::value, op_id)) { + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; + const uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + son_wallet_update_operation swu_op; + swu_op.payer = gpo.parameters.son_account(); + swu_op.son_wallet_id = op_id; + swu_op.sidechain = sidechain; + swu_op.address = wallet_contract_address; + proposal_op.proposed_ops.emplace_back(swu_op); + + const auto signers = [this, &prev_sw, &active_sw, &swi] { + std::vector signers; + //! Check if we don't have any previous set of active SONs use the current one + if (prev_sw != swi.rend()) { + if (!prev_sw->sons.at(sidechain).empty()) + signers = prev_sw->sons.at(sidechain); + else + signers = active_sw->sons.at(sidechain); + } else { + signers = active_sw->sons.at(sidechain); + } + + return signers; + }(); + + std::string tx_str = create_primary_wallet_transaction(gpo.active_sons.at(sidechain), op_id.operator std::string()); + if (!tx_str.empty()) { + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = op_id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + for (const auto &signer : signers) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } + proposal_op.proposed_ops.emplace_back(stc_op); + } + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + plugin.log_son_proposal_retry(sidechain, chain::operation::tag::value, op_id); + } catch (fc::exception &e) { + elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what())); + return; + } + } + } +} + +void sidechain_net_handler_ethereum::process_sidechain_addresses() { +} + +bool sidechain_net_handler_ethereum::process_deposit(const son_wallet_deposit_object &swdo) { + + if (proposal_exists(chain::operation::tag::value, swdo.id)) { + return false; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + const auto &assets_by_symbol = database.get_index_type().indices().get(); + const auto asset_itr = assets_by_symbol.find(swdo.sidechain_currency); + if (asset_itr == assets_by_symbol.end()) { + wlog("Could not find asset: ${symbol}", ("symbol", swdo.sidechain_currency)); + return false; + } + + const price asset_price = asset_itr->options.core_exchange_rate; + const asset asset_to_issue = asset(swdo.peerplays_asset.amount * asset_price.quote.amount / asset_price.base.amount, asset_itr->get_id()); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; + const uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + son_wallet_deposit_process_operation swdp_op; + swdp_op.payer = gpo.parameters.son_account(); + swdp_op.son_wallet_deposit_id = swdo.id; + proposal_op.proposed_ops.emplace_back(swdp_op); + + asset_issue_operation ai_op; + ai_op.fee = database.current_fee_schedule().calculate_fee(ai_op); + ai_op.issuer = gpo.parameters.son_account(); + ai_op.asset_to_issue = asset_to_issue; + ai_op.issue_to_account = swdo.peerplays_from; + proposal_op.proposed_ops.emplace_back(ai_op); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception &e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + + return false; +} + +bool sidechain_net_handler_ethereum::process_withdrawal(const son_wallet_withdraw_object &swwo) { + + if (proposal_exists(chain::operation::tag::value, swwo.id)) { + return false; + } + + std::string tx_str = create_withdrawal_transaction(swwo); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; + const uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + son_wallet_withdraw_process_operation swwp_op; + swwp_op.payer = gpo.parameters.son_account(); + swwp_op.son_wallet_withdraw_id = swwo.id; + proposal_op.proposed_ops.emplace_back(swwp_op); + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swwo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + for (const auto &signer : gpo.active_sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } + proposal_op.proposed_ops.emplace_back(stc_op); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); + try { + trx.validate(); + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception &e) { + elog("Sending proposal for withdraw sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +std::string sidechain_net_handler_ethereum::process_sidechain_transaction(const sidechain_transaction_object &sto) { + return sign_transaction(sto); +} + +std::string sidechain_net_handler_ethereum::send_sidechain_transaction(const sidechain_transaction_object &sto) { + boost::property_tree::ptree pt; + boost::property_tree::ptree pt_array; + + std::vector transactions; + for (const auto &signature : sto.signatures) { + + //! Check if we have this signed transaction, if not, don't send it + if (signature.second.empty()) + continue; + + ethereum::encoded_sign_transaction transaction{sto.transaction, ethereum::signature{signature.second}}; + transactions.emplace_back(transaction); + } + + const auto ¤t_son = plugin.get_current_son_object(sidechain); + FC_ASSERT(current_son.sidechain_public_keys.contains(sidechain), "No public keys for current son: ${account_id}", ("account_id", current_son.son_account)); + const auto &public_key = current_son.sidechain_public_keys.at(sidechain); + + const auto function_signature = ethereum::signature_encoder::get_function_signature_from_transaction(sto.transaction); + if (function_signature.empty()) { + elog("Function signature is empty for transaction id ${id}, transaction ${transaction}", ("id", sto.id)("transaction", sto.transaction)); + return std::string{}; //! Return empty string, as we have error in sending + } + + const ethereum::signature_encoder encoder{function_signature}; +#ifdef SEND_RAW_TRANSACTION + const auto data = encoder.encode(transactions); + const std::string params = "[{\"from\":\"" + ethereum::add_0x(public_key) + "\", \"to\":\"" + wallet_contract_address + "\", \"data\":\"" + data + "\"}]"; + + ethereum::raw_transaction raw_tr; + raw_tr.nonce = rpc_client->get_nonce(ethereum::add_0x(public_key)); + raw_tr.gas_price = rpc_client->get_gas_price(); + raw_tr.gas_limit = rpc_client->get_estimate_gas(params); + if (raw_tr.gas_limit.empty()) + raw_tr.gas_limit = rpc_client->get_gas_limit(); + raw_tr.to = wallet_contract_address; + raw_tr.value = ""; + raw_tr.data = data; + raw_tr.chain_id = ethereum::add_0x(ethereum::to_hex(chain_id)); + + const auto sign_tr = raw_tr.sign(get_private_key(public_key)); + const std::string sidechain_transaction = rpc_client->eth_send_raw_transaction(sign_tr.serialize()); +#else + ethereum::transaction raw_tr; + raw_tr.data = encoder.encode(transactions); + raw_tr.to = wallet_contract_address; + raw_tr.from = ethereum::add_0x(public_key); + + const auto sign_tr = raw_tr.sign(get_private_key(public_key)); + const std::string sidechain_transaction = rpc_client->eth_send_transaction(sign_tr.serialize()); +#endif + + std::stringstream ss_tx(sidechain_transaction); + boost::property_tree::ptree tx_json; + boost::property_tree::read_json(ss_tx, tx_json); + if (tx_json.count("result") && !tx_json.count("error")) { + boost::property_tree::ptree node; + node.put("transaction", sto.transaction); + node.put("sidechain_transaction", sidechain_transaction); + node.put("transaction_receipt", tx_json.get("result")); + pt_array.push_back(std::make_pair("", node)); + } else { + //! Fixme + //! How should we proceed with error in eth_send_transaction + elog("Error in eth send transaction for transaction id ${id}, transaction ${transaction}, sidechain_transaction ${sidechain_transaction}", ("id", sto.id)("transaction", sto.transaction)("sidechain_transaction", sidechain_transaction)); + return std::string{}; //! Return empty string, as we have error in sending + } + pt.add_child("result_array", pt_array); + + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, pt); + return ss.str(); +} + +bool sidechain_net_handler_ethereum::settle_sidechain_transaction(const sidechain_transaction_object &sto, asset &settle_amount) { + std::stringstream ss(sto.sidechain_transaction); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (!json.count("result_array")) { + return false; + } + + size_t count = 0; + for (const auto &entry : json.get_child("result_array")) { + const std::string receipt = rpc_client->eth_get_transaction_receipt(entry.second.get("transaction_receipt")); + + std::stringstream ss_receipt(receipt); + boost::property_tree::ptree json_receipt; + boost::property_tree::read_json(ss_receipt, json_receipt); + + if (json_receipt.get("result") == "null") { + wlog("Block is not minted yet for transaction ${id}", ("id", sto.id)); + return false; + } + + if ("0x1" == json_receipt.get("result.status")) { + count += 1; + //! Fixme - compare data somehow? + //if( sto.transaction == entry_receipt.second.get("data") ) { + //} + } + } + + //! Check that we have all transactions + if (count != json.get_child("result_array").size()) { + wlog("Not all receipts received for transaction ${id}", ("id", sto.id)); + return false; + } else { + if (sto.object_id.is()) { + settle_amount = asset(0, database.get_global_properties().parameters.eth_asset()); + } + + if (sto.object_id.is()) { + auto swwo = database.get(sto.object_id); + const auto &assets_by_symbol = database.get_index_type().indices().get(); + const auto asset_itr = assets_by_symbol.find(swwo.withdraw_currency); + if (asset_itr == assets_by_symbol.end()) { + wlog("Could not find asset: ${symbol}", ("symbol", swwo.withdraw_currency)); + return false; + } + settle_amount = asset(swwo.withdraw_amount, asset_itr->get_id()); + } + + return true; + } + + return false; +} + +optional sidechain_net_handler_ethereum::estimate_withdrawal_transaction_fee() const { + const auto &gpo = database.get_global_properties(); + if (gpo.active_sons.at(sidechain).empty()) { + wlog("No active sons for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional{}; + } + + const auto &active_son = gpo.active_sons.at(sidechain).at(0); + const auto &s_idx = database.get_index_type().indices().get(); + const auto son = s_idx.find(active_son.son_id); + if (son == s_idx.end()) { + wlog("Can't find son for id: ${son_id}", ("son_id", active_son.son_id)); + return optional{}; + } + + if (!son->sidechain_public_keys.contains(sidechain)) { + wlog("No public keys for current son: ${account_id}", ("account_id", son->son_account)); + return optional{}; + } + + const auto &public_key = son->sidechain_public_keys.at(sidechain); + const auto data = ethereum::withdrawal_encoder::encode(public_key, boost::multiprecision::uint256_t{1} * boost::multiprecision::uint256_t{10000000000}, "0"); + const std::string params = "[{\"from\":\"" + ethereum::add_0x(public_key) + "\", \"to\":\"" + wallet_contract_address + "\", \"data\":\"" + data + "\"}]"; + + const auto estimate_gas = ethereum::from_hex(rpc_client->get_estimate_gas(params)); + const auto gas_price = ethereum::from_hex(rpc_client->get_gas_price()); + const auto eth_gas_fee = double(estimate_gas * gas_price) / double{1000000000000000000}; + + const auto asset = database.get(database.get_global_properties().parameters.eth_asset()); + return asset.amount_from_string(std::to_string(eth_gas_fee)); +} + +std::string sidechain_net_handler_ethereum::create_primary_wallet_transaction(const std::vector &son_pubkeys, const std::string &object_id) { + std::vector> owners_weights; + for (auto &son : son_pubkeys) { + const std::string pub_key_str = son.public_key; + owners_weights.emplace_back(std::make_pair(pub_key_str, son.weight)); + } + + return ethereum::update_owners_encoder::encode(owners_weights, object_id); +} + +std::string sidechain_net_handler_ethereum::create_withdrawal_transaction(const son_wallet_withdraw_object &swwo) { + if (swwo.withdraw_currency == "ETH") { + return ethereum::withdrawal_encoder::encode(ethereum::remove_0x(swwo.withdraw_address), boost::multiprecision::uint256_t{swwo.withdraw_amount.value} * boost::multiprecision::uint256_t{10000000000}, swwo.id.operator std::string()); + } else { + const auto it = erc20_addresses.left.find(swwo.withdraw_currency); + if (it == erc20_addresses.left.end()) { + elog("No erc-20 token: ${symbol}", ("symbol", swwo.withdraw_currency)); + return ""; + } + return ethereum::withdrawal_erc20_encoder::encode(ethereum::remove_0x(it->second), ethereum::remove_0x(swwo.withdraw_address), boost::multiprecision::uint256_t{swwo.withdraw_amount.value}, swwo.id.operator std::string()); + } + + return ""; +} + +std::string sidechain_net_handler_ethereum::sign_transaction(const sidechain_transaction_object &sto) { + const auto ¤t_son = plugin.get_current_son_object(sidechain); + FC_ASSERT(current_son.sidechain_public_keys.contains(sidechain), "No public keys for current son: ${account_id}", ("account_id", current_son.son_account)); + + const auto &public_key = current_son.sidechain_public_keys.at(sidechain); + + //! We need to change v value according to chain_id + auto signature = ethereum::sign_hash(ethereum::keccak_hash(sto.transaction), ethereum::add_0x(ethereum::to_hex(chain_id)), get_private_key(public_key)); + signature.v = ethereum::to_hex(ethereum::from_hex(signature.v) - 2 * chain_id - 35 + 27); + + return signature.serialize(); +} + +void sidechain_net_handler_ethereum::schedule_ethereum_listener() { + const fc::time_point now = fc::time_point::now(); + const int64_t time_to_next = 5000; + + const fc::time_point next_wakeup(now + fc::milliseconds(time_to_next)); + + _listener_task = fc::schedule([this] { + ethereum_listener_loop(); + }, + next_wakeup, "SON Ethereum listener task"); +} + +void sidechain_net_handler_ethereum::ethereum_listener_loop() { + schedule_ethereum_listener(); + + const auto reply = rpc_client->eth_blockNumber(); + + if (!reply.empty()) { + const uint64_t head_block_number = ethereum::from_hex(reply); + + if (head_block_number != last_block_received) { + //! Check that current block number is greater than last one + if (head_block_number < last_block_received) { + wlog("Head block ${head_block_number} is greater than last received block ${last_block_received}", ("head_block_number", head_block_number)("last_block_received", last_block_received)); + return; + } + + //! Send event data for all blocks that passed + for (uint64_t i = last_block_received + 1; i <= head_block_number; ++i) { + const std::string block_number = ethereum::add_0x(ethereum::to_hex(i, false)); + handle_event(block_number); + } + + last_block_received = head_block_number; + } + } +} + +void sidechain_net_handler_ethereum::handle_event(const std::string &block_number) { + const std::string block = rpc_client->eth_get_block_by_number(block_number, true); + if (block != "") { + add_to_son_listener_log("BLOCK : " + block_number); + std::stringstream ss(block); + boost::property_tree::ptree block_json; + boost::property_tree::read_json(ss, block_json); + + if (block_json.get("result") == "null") { + wlog("No data for block ${block_number}", ("block_number", block_number)); + return; + } + + size_t tx_idx = -1; + for (const auto &tx_child : block_json.get_child("result.transactions")) { + const boost::property_tree::ptree tx = tx_child.second; + tx_idx = tx_idx + 1; + + const std::string to = tx.get("to"); + std::string from = tx.get("from"); + std::transform(from.begin(), from.end(), from.begin(), ::tolower); + + std::string cmp_to = to; + std::transform(cmp_to.begin(), cmp_to.end(), cmp_to.begin(), ::toupper); + std::string cmp_wallet_contract_address = wallet_contract_address; + std::transform(cmp_wallet_contract_address.begin(), cmp_wallet_contract_address.end(), cmp_wallet_contract_address.begin(), ::toupper); + + if (cmp_to == cmp_wallet_contract_address) { + + //! Check whether it is ERC-20 token deposit + std::string symbol; + boost::multiprecision::uint256_t amount; + const auto deposit_erc_20 = ethereum::deposit_erc20_decoder::decode(tx.get("input")); + if (deposit_erc_20.valid()) { + std::string cmp_token = deposit_erc_20->token; + std::transform(cmp_token.begin(), cmp_token.end(), cmp_token.begin(), ::tolower); + + const auto it = erc20_addresses.right.find(cmp_token); + if (it == erc20_addresses.right.end()) { + wlog("No erc-20 token with address: ${address}", ("address", cmp_token)); + continue; + } + symbol = it->second; + amount = deposit_erc_20->amount; + } else { + symbol = "ETH"; + const std::string value_s = tx.get("value"); + amount = boost::multiprecision::uint256_t{value_s}; + amount = amount / 100000; + amount = amount / 100000; + } + + const auto &assets_by_symbol = database.get_index_type().indices().get(); + const auto asset_itr = assets_by_symbol.find(symbol); + if (asset_itr == assets_by_symbol.end()) { + wlog("Could not find asset: ${symbol}", ("symbol", symbol)); + continue; + } + + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, from, time_point_sec::maximum())); + if (addr_itr == sidechain_addresses_idx.end()) { + continue; + } + + std::stringstream ss; + ss << "ethereum" + << "-" << tx.get("hash") << "-" << tx_idx; + + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.block_num = database.head_block_num(); + sed.sidechain = sidechain; + sed.type = sidechain_event_type::deposit; + sed.sidechain_uid = ss.str(); + sed.sidechain_transaction_id = tx.get("hash"); + sed.sidechain_from = from; + sed.sidechain_to = to; + sed.sidechain_currency = symbol; + sed.sidechain_amount = amount; + sed.peerplays_from = addr_itr->sidechain_address_account; + sed.peerplays_to = database.get_global_properties().parameters.son_account(); + const price price = asset_itr->options.core_exchange_rate; + sed.peerplays_asset = asset(sed.sidechain_amount * price.base.amount / price.quote.amount); + + add_to_son_listener_log("TRX : " + sed.sidechain_transaction_id); + + sidechain_event_data_received(sed); + } + } + } +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_factory.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_factory.cpp new file mode 100644 index 00000000..cd6b50be --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_factory.cpp @@ -0,0 +1,35 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler_factory::sidechain_net_handler_factory(peerplays_sidechain_plugin &_plugin) : + plugin(_plugin) { +} + +std::unique_ptr sidechain_net_handler_factory::create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options) const { + switch (sidechain) { + case sidechain_type::bitcoin: { + return std::unique_ptr(new sidechain_net_handler_bitcoin(plugin, options)); + } + case sidechain_type::hive: { + return std::unique_ptr(new sidechain_net_handler_hive(plugin, options)); + } + case sidechain_type::ethereum: { + return std::unique_ptr(new sidechain_net_handler_ethereum(plugin, options)); + } + case sidechain_type::peerplays: { + return std::unique_ptr(new sidechain_net_handler_peerplays(plugin, options)); + } + default: + assert(false); + } + + return nullptr; +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp index d63e6743..eb2f332f 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp @@ -20,7 +20,7 @@ #include #include #include -#include +#include #include #include #include @@ -28,25 +28,23 @@ #include #include -#include - namespace graphene { namespace peerplays_sidechain { -hive_node_rpc_client::hive_node_rpc_client(const std::string &url, const std::string &user_name, const std::string &password, bool debug_rpc_calls) : - rpc_client(url, user_name, password, debug_rpc_calls) { +hive_rpc_client::hive_rpc_client(const std::vector &credentials, bool debug_rpc_calls, bool simulate_connection_reselection) : + rpc_client(sidechain_type::hive, credentials, debug_rpc_calls, simulate_connection_reselection) { } -std::string hive_node_rpc_client::account_history_api_get_transaction(std::string transaction_id) { - std::string params = "{ \"id\": \"" + transaction_id + "\" }"; +std::string hive_rpc_client::account_history_api_get_transaction(std::string transaction_id) { + const std::string params = "{ \"id\": \"" + transaction_id + "\" }"; return send_post_request("account_history_api.get_transaction", params, debug_rpc_calls); } -std::string hive_node_rpc_client::block_api_get_block(uint32_t block_number) { - std::string params = "{ \"block_num\": " + std::to_string(block_number) + " }"; +std::string hive_rpc_client::block_api_get_block(uint32_t block_number) { + const std::string params = "{ \"block_num\": " + std::to_string(block_number) + " }"; return send_post_request("block_api.get_block", params, debug_rpc_calls); } -std::string hive_node_rpc_client::condenser_api_get_accounts(std::vector accounts) { +std::string hive_rpc_client::condenser_api_get_accounts(std::vector accounts) { std::string params = ""; for (auto account : accounts) { if (!params.empty()) { @@ -58,82 +56,98 @@ std::string hive_node_rpc_client::condenser_api_get_accounts(std::vector accounts; accounts.push_back(account); std::string reply_str = condenser_api_get_accounts(accounts); return retrieve_array_value_from_reply(reply_str, "", 0); } -std::string hive_node_rpc_client::get_account_memo_key(std::string account) { +std::string hive_rpc_client::get_account_memo_key(std::string account) { std::string reply_str = get_account(account); reply_str = "{\"result\":" + reply_str + "}"; return retrieve_value_from_reply(reply_str, "memo_key"); } -std::string hive_node_rpc_client::get_chain_id() { - std::string reply_str = database_api_get_version(); +std::string hive_rpc_client::get_chain_id() { + const std::string reply_str = database_api_get_version(); return retrieve_value_from_reply(reply_str, "chain_id"); } -std::string hive_node_rpc_client::get_head_block_id() { - std::string reply_str = database_api_get_dynamic_global_properties(); +std::string hive_rpc_client::get_head_block_id() { + const std::string reply_str = database_api_get_dynamic_global_properties(); return retrieve_value_from_reply(reply_str, "head_block_id"); } -std::string hive_node_rpc_client::get_head_block_time() { - std::string reply_str = database_api_get_dynamic_global_properties(); +std::string hive_rpc_client::get_head_block_time() { + const std::string reply_str = database_api_get_dynamic_global_properties(); return retrieve_value_from_reply(reply_str, "time"); } -std::string hive_node_rpc_client::get_is_test_net() { - std::string reply_str = condenser_api_get_config(); +std::string hive_rpc_client::get_is_test_net() { + const std::string reply_str = condenser_api_get_config(); return retrieve_value_from_reply(reply_str, "IS_TEST_NET"); } -std::string hive_node_rpc_client::get_last_irreversible_block_num() { - std::string reply_str = database_api_get_dynamic_global_properties(); +std::string hive_rpc_client::get_last_irreversible_block_num() { + const std::string reply_str = database_api_get_dynamic_global_properties(); return retrieve_value_from_reply(reply_str, "last_irreversible_block_num"); } +uint64_t hive_rpc_client::ping(rpc_connection &conn) const { + const std::string reply = send_post_request(conn, "database_api.get_dynamic_global_properties", "", debug_rpc_calls); + if (!reply.empty()) { + std::stringstream ss(reply); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + if (json.count("result")) + return json.get("result.head_block_number"); + } + return std::numeric_limits::max(); +} + sidechain_net_handler_hive::sidechain_net_handler_hive(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : - sidechain_net_handler(_plugin, options) { - sidechain = sidechain_type::hive; + sidechain_net_handler(sidechain_type::hive, _plugin, options) { if (options.count("debug-rpc-calls")) { debug_rpc_calls = options.at("debug-rpc-calls").as(); } + bool simulate_connection_reselection = options.at("simulate-rpc-connection-reselection").as(); - node_rpc_url = options.at("hive-node-rpc-url").as(); - if (options.count("hive-node-rpc-user")) { - node_rpc_user = options.at("hive-node-rpc-user").as(); + std::vector rpc_urls = options.at("hive-node-rpc-url").as>(); + std::string rpc_user; + if (options.count("hive-rpc-user")) { + rpc_user = options.at("hive-rpc-user").as(); } else { - node_rpc_user = ""; + rpc_user = ""; } - if (options.count("hive-node-rpc-password")) { - node_rpc_password = options.at("hive-node-rpc-password").as(); + std::string rpc_password; + if (options.count("hive-rpc-password")) { + rpc_password = options.at("hive-rpc-password").as(); } else { - node_rpc_password = ""; + rpc_password = ""; } + wallet_account_name = options.at("hive-wallet-account-name").as(); + if (options.count("hive-private-key")) { const std::vector pub_priv_keys = options["hive-private-key"].as>(); for (const std::string &itr_key_pair : pub_priv_keys) { @@ -146,16 +160,25 @@ sidechain_net_handler_hive::sidechain_net_handler_hive(peerplays_sidechain_plugi } } - node_rpc_client = new hive_node_rpc_client(node_rpc_url, node_rpc_user, node_rpc_password, debug_rpc_calls); + for (size_t i = 0; i < rpc_urls.size(); i++) { + rpc_credentials creds; + creds.url = rpc_urls[i]; + creds.user = rpc_user; + creds.password = rpc_password; + _rpc_credentials.push_back(creds); + } + FC_ASSERT(!_rpc_credentials.empty()); - std::string chain_id_str = node_rpc_client->get_chain_id(); + rpc_client = new hive_rpc_client(_rpc_credentials, debug_rpc_calls, simulate_connection_reselection); + + const std::string chain_id_str = rpc_client->get_chain_id(); if (chain_id_str.empty()) { - elog("No Hive node running at ${url}", ("url", node_rpc_url)); + elog("No Hive node running at ${url}", ("url", _rpc_credentials[0].url)); FC_ASSERT(false); } chain_id = chain_id_type(chain_id_str); - std::string is_test_net = node_rpc_client->get_is_test_net(); + const std::string is_test_net = rpc_client->get_is_test_net(); network_type = is_test_net.compare("true") == 0 ? hive::network::testnet : hive::network::mainnet; if (network_type == hive::network::mainnet) { ilog("Running on Hive mainnet, chain id ${chain_id_str}", ("chain_id_str", chain_id_str)); @@ -180,7 +203,8 @@ sidechain_net_handler_hive::~sidechain_net_handler_hive() { } bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { - //ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id(sidechain))); bool should_approve = false; @@ -209,12 +233,15 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { bool address_ok = false; bool transaction_ok = false; son_wallet_id_type swo_id = op_obj_idx_0.get().son_wallet_id; + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = (swo_id.instance.value - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const son_wallet_id_type op_id{id}; const auto &idx = database.get_index_type().indices().get(); - const auto swo = idx.find(swo_id); + const auto swo = idx.find(op_id); if (swo != idx.end()) { - auto active_sons = gpo.active_sons; - vector wallet_sons = swo->sons; + const auto &active_sons = gpo.active_sons.at(sidechain); + const auto &wallet_sons = swo->sons.at(sidechain); bool son_sets_equal = (active_sons.size() == wallet_sons.size()); @@ -225,11 +252,13 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { } if (son_sets_equal) { - address_ok = (op_obj_idx_0.get().address == "son-account"); + address_ok = (op_obj_idx_0.get().address == wallet_account_name); } if (po.proposed_transaction.operations.size() >= 2) { - object_id_type object_id = op_obj_idx_1.get().object_id; + const object_id_type object_id = op_obj_idx_1.get().object_id; + const auto id = (object_id.instance() - std::distance(ast.begin(), ast.find(sidechain))) / ast.size(); + const object_id_type obj_id{object_id.space(), object_id.type(), id}; std::string op_tx_str = op_obj_idx_1.get().transaction; const auto &st_idx = database.get_index_type().indices().get(); @@ -238,9 +267,9 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { std::string tx_str = ""; - if (object_id.is()) { + if (obj_id.is()) { const auto &idx = database.get_index_type().indices().get(); - const auto swo = idx.find(object_id); + const auto swo = idx.find(obj_id); if (swo != idx.end()) { std::stringstream ss_trx(boost::algorithm::unhex(op_tx_str)); @@ -251,18 +280,19 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { uint32_t total_weight = 0; for (const auto &wallet_son : wallet_sons) { total_weight = total_weight + wallet_son.weight; - account_auths[wallet_son.sidechain_public_keys.at(sidechain)] = wallet_son.weight; + account_auths[wallet_son.public_key] = wallet_son.weight; } - std::string memo_key = node_rpc_client->get_account_memo_key("son-account"); + const std::string memo_key = rpc_client->get_account_memo_key(wallet_account_name); - hive::authority active; - active.weight_threshold = total_weight * 2 / 3 + 1; - active.account_auths = account_auths; + hive::authority a; + a.weight_threshold = total_weight * 2 / 3 + 1; + a.account_auths = account_auths; hive::account_update_operation auo; - auo.account = "son-account"; - auo.active = active; + auo.account = wallet_account_name; + auo.owner = a; + auo.active = a; auo.memo_key = op_trx.operations[0].get().memo_key; hive::signed_transaction htrx; @@ -303,7 +333,7 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { uint64_t swdo_sidechain_amount = swdo->sidechain_amount.value; uint64_t swdo_op_idx = std::stoll(swdo->sidechain_uid.substr(swdo->sidechain_uid.find_last_of("-"))); - std::string tx_str = node_rpc_client->account_history_api_get_transaction(swdo_txid); + const std::string tx_str = rpc_client->account_history_api_get_transaction(swdo_txid); if (tx_str != "") { std::stringstream ss_tx(tx_str); @@ -408,7 +438,7 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { } hive::transfer_operation t_op; - t_op.from = "son-account"; + t_op.from = wallet_account_name; t_op.to = swwo->withdraw_address; t_op.amount.amount = swwo->withdraw_amount; t_op.amount.symbol = symbol; @@ -478,42 +508,55 @@ void sidechain_net_handler_hive::process_primary_wallet() { const auto &active_sw = swi.rbegin(); if (active_sw != swi.rend()) { + const auto &prev_sw = std::next(active_sw); + if (prev_sw != swi.rend() && active_sw->sons.at(sidechain) == prev_sw->sons.at(sidechain)) + return; + if ((active_sw->addresses.find(sidechain) == active_sw->addresses.end()) || (active_sw->addresses.at(sidechain).empty())) { - if (proposal_exists(chain::operation::tag::value, active_sw->id)) { + const auto ast = active_sidechain_types(database.head_block_time()); + const auto id = active_sw->id.instance() * ast.size() + std::distance(ast.begin(), ast.find(sidechain)); + const object_id_type op_id{active_sw->id.space(), active_sw->id.type(), id}; + + if (proposal_exists(chain::operation::tag::value, op_id)) { + return; + } + + if (!plugin.can_son_participate(sidechain, chain::operation::tag::value, op_id)) { return; } const chain::global_property_object &gpo = database.get_global_properties(); - auto active_sons = gpo.active_sons; + const auto &active_sons = gpo.active_sons.at(sidechain); fc::flat_map account_auths; uint32_t total_weight = 0; for (const auto &active_son : active_sons) { total_weight = total_weight + active_son.weight; - account_auths[active_son.sidechain_public_keys.at(sidechain)] = active_son.weight; + account_auths[active_son.public_key] = active_son.weight; } - std::string memo_key = node_rpc_client->get_account_memo_key("son-account"); + const std::string memo_key = rpc_client->get_account_memo_key(wallet_account_name); if (memo_key.empty()) { return; } - hive::authority active; - active.weight_threshold = total_weight * 2 / 3 + 1; - active.account_auths = account_auths; + hive::authority a; + a.weight_threshold = total_weight * 2 / 3 + 1; + a.account_auths = account_auths; hive::account_update_operation auo; - auo.account = "son-account"; - auo.active = active; + auo.account = wallet_account_name; + auo.owner = a; + auo.active = a; auo.memo_key = hive::public_key_type(memo_key); - std::string block_id_str = node_rpc_client->get_head_block_id(); + const std::string block_id_str = rpc_client->get_head_block_id(); hive::block_id_type head_block_id(block_id_str); - std::string head_block_time_str = node_rpc_client->get_head_block_time(); + const std::string head_block_time_str = rpc_client->get_head_block_time(); time_point head_block_time = fc::time_point_sec::from_iso_string(head_block_time_str); hive::signed_transaction htrx; @@ -530,28 +573,49 @@ void sidechain_net_handler_hive::process_primary_wallet() { } proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); son_wallet_update_operation swu_op; swu_op.payer = gpo.parameters.son_account(); - swu_op.son_wallet_id = active_sw->id; + swu_op.son_wallet_id = op_id; swu_op.sidechain = sidechain; - swu_op.address = "son-account"; + swu_op.address = wallet_account_name; proposal_op.proposed_ops.emplace_back(swu_op); + const auto signers = [this, &prev_sw, &active_sw, &swi] { + std::vector signers; + //! Check if we don't have any previous set of active SONs use the current one + if (prev_sw != swi.rend()) { + if (!prev_sw->sons.at(sidechain).empty()) + signers = prev_sw->sons.at(sidechain); + else + signers = active_sw->sons.at(sidechain); + } else { + signers = active_sw->sons.at(sidechain); + } + + return signers; + }(); + sidechain_transaction_create_operation stc_op; stc_op.payer = gpo.parameters.son_account(); - stc_op.object_id = active_sw->id; + stc_op.object_id = op_id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = gpo.active_sons; - + for (const auto &signer : signers) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_op.proposed_ops.emplace_back(stc_op); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -575,7 +639,7 @@ void sidechain_net_handler_hive::process_sidechain_addresses() { if (sao.expires == time_point_sec::maximum()) { if (sao.deposit_address == "") { sidechain_address_update_operation op; - op.payer = plugin.get_current_son_object().son_account; + op.payer = plugin.get_current_son_object(sidechain).son_account; op.sidechain_address_id = sao.id; op.sidechain_address_account = sao.sidechain_address_account; op.sidechain = sao.sidechain; @@ -585,7 +649,7 @@ void sidechain_net_handler_hive::process_sidechain_addresses() { op.withdraw_public_key = sao.withdraw_public_key; op.withdraw_address = sao.withdraw_address; - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -603,6 +667,11 @@ void sidechain_net_handler_hive::process_sidechain_addresses() { } bool sidechain_net_handler_hive::process_deposit(const son_wallet_deposit_object &swdo) { + + if (proposal_exists(chain::operation::tag::value, swdo.id)) { + return false; + } + const chain::global_property_object &gpo = database.get_global_properties(); price asset_price; @@ -617,7 +686,7 @@ bool sidechain_net_handler_hive::process_deposit(const son_wallet_deposit_object } proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -633,7 +702,7 @@ bool sidechain_net_handler_hive::process_deposit(const son_wallet_deposit_object ai_op.issue_to_account = swdo.peerplays_from; proposal_op.proposed_ops.emplace_back(ai_op); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -649,6 +718,11 @@ bool sidechain_net_handler_hive::process_deposit(const son_wallet_deposit_object } bool sidechain_net_handler_hive::process_withdrawal(const son_wallet_withdraw_object &swwo) { + + if (proposal_exists(chain::operation::tag::value, swwo.id)) { + return false; + } + const chain::global_property_object &gpo = database.get_global_properties(); //===== @@ -662,16 +736,16 @@ bool sidechain_net_handler_hive::process_withdrawal(const son_wallet_withdraw_ob } hive::transfer_operation t_op; - t_op.from = "son-account"; + t_op.from = wallet_account_name; t_op.to = swwo.withdraw_address; t_op.amount.amount = swwo.withdraw_amount; t_op.amount.symbol = symbol; t_op.memo = ""; - std::string block_id_str = node_rpc_client->get_head_block_id(); + const std::string block_id_str = rpc_client->get_head_block_id(); hive::block_id_type head_block_id(block_id_str); - std::string head_block_time_str = node_rpc_client->get_head_block_time(); + const std::string head_block_time_str = rpc_client->get_head_block_time(); time_point head_block_time = fc::time_point_sec::from_iso_string(head_block_time_str); hive::signed_transaction htrx; @@ -690,7 +764,7 @@ bool sidechain_net_handler_hive::process_withdrawal(const son_wallet_withdraw_ob //===== proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); @@ -704,10 +778,17 @@ bool sidechain_net_handler_hive::process_withdrawal(const son_wallet_withdraw_ob stc_op.object_id = swwo.id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = gpo.active_sons; + for (const auto &signer : gpo.active_sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_op.proposed_ops.emplace_back(stc_op); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -727,10 +808,10 @@ std::string sidechain_net_handler_hive::process_sidechain_transaction(const side hive::signed_transaction htrx; fc::raw::unpack(ss_trx, htrx, 1000); - std::string chain_id_str = node_rpc_client->get_chain_id(); + const std::string chain_id_str = rpc_client->get_chain_id(); const hive::chain_id_type chain_id(chain_id_str); - fc::optional privkey = graphene::utilities::wif_to_key(get_private_key(plugin.get_current_son_object().sidechain_public_keys.at(sidechain))); + fc::optional privkey = graphene::utilities::wif_to_key(get_private_key(plugin.get_current_son_object(sidechain).sidechain_public_keys.at(sidechain))); signature_type st = htrx.sign(*privkey, chain_id); std::stringstream ss_st; @@ -755,7 +836,7 @@ std::string sidechain_net_handler_hive::send_sidechain_transaction(const sidecha } std::string params = fc::json::to_string(htrx); - node_rpc_client->network_broadcast_api_broadcast_transaction(params); + rpc_client->network_broadcast_api_broadcast_transaction(params); return htrx.id().str(); } @@ -770,7 +851,7 @@ bool sidechain_net_handler_hive::settle_sidechain_transaction(const sidechain_tr return false; } - std::string tx_str = node_rpc_client->account_history_api_get_transaction(sto.sidechain_transaction); + const std::string tx_str = rpc_client->account_history_api_get_transaction(sto.sidechain_transaction); if (tx_str != "") { std::stringstream ss_tx(tx_str); @@ -781,7 +862,7 @@ bool sidechain_net_handler_hive::settle_sidechain_transaction(const sidechain_tr std::string tx_txid = tx_json.get("result.transaction_id"); uint32_t tx_block_num = tx_json.get("result.block_num"); - uint32_t last_irreversible_block = std::stoul(node_rpc_client->get_last_irreversible_block_num()); + const uint32_t last_irreversible_block = std::stoul(rpc_client->get_last_irreversible_block_num()); //std::string tx_address = addr.get_address(); //int64_t tx_amount = -1; @@ -802,6 +883,11 @@ bool sidechain_net_handler_hive::settle_sidechain_transaction(const sidechain_tr return false; } +optional sidechain_net_handler_hive::estimate_withdrawal_transaction_fee() const { + wlog("estimate_withdrawal_transaction_fee not implemented for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional{}; +} + void sidechain_net_handler_hive::schedule_hive_listener() { fc::time_point now = fc::time_point::now(); int64_t time_to_next = 1000; @@ -817,7 +903,7 @@ void sidechain_net_handler_hive::schedule_hive_listener() { void sidechain_net_handler_hive::hive_listener_loop() { schedule_hive_listener(); - std::string reply = node_rpc_client->database_api_get_dynamic_global_properties(); + const std::string reply = rpc_client->database_api_get_dynamic_global_properties(); if (!reply.empty()) { std::stringstream ss(reply); boost::property_tree::ptree json; @@ -832,7 +918,7 @@ void sidechain_net_handler_hive::hive_listener_loop() { } } - //std::string reply = node_rpc_client->get_last_irreversible_block_num(); + //std::string reply = rpc_client->get_last_irreversible_block_num(); //if (!reply.empty()) { // uint64_t last_irreversible_block = std::stoul(reply); // if (last_irreversible_block != last_block_received) { @@ -844,7 +930,7 @@ void sidechain_net_handler_hive::hive_listener_loop() { } void sidechain_net_handler_hive::handle_event(const std::string &event_data) { - std::string block = node_rpc_client->block_api_get_block(std::atoll(event_data.c_str())); + const std::string block = rpc_client->block_api_get_block(std::atoll(event_data.c_str())); if (block != "") { add_to_son_listener_log("BLOCK : " + event_data); std::stringstream ss(block); @@ -869,7 +955,7 @@ void sidechain_net_handler_hive::handle_event(const std::string &event_data) { std::string from = op_value.get("from"); std::string to = op_value.get("to"); - if (to == "son-account") { + if (to == wallet_account_name) { const auto &amount_child = op_value.get_child("amount"); @@ -924,6 +1010,7 @@ void sidechain_net_handler_hive::handle_event(const std::string &event_data) { sed.timestamp = database.head_block_time(); sed.block_num = database.head_block_num(); sed.sidechain = sidechain; + sed.type = sidechain_event_type::deposit; sed.sidechain_uid = sidechain_uid; sed.sidechain_transaction_id = transaction_id; sed.sidechain_from = from; diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp index d5a9ec32..edf92e80 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp @@ -23,8 +23,7 @@ namespace graphene { namespace peerplays_sidechain { sidechain_net_handler_peerplays::sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : - sidechain_net_handler(_plugin, options) { - sidechain = sidechain_type::peerplays; + sidechain_net_handler(sidechain_type::peerplays, _plugin, options) { //const auto &assets_by_symbol = database.get_index_type().indices().get(); //const auto get_asset_id = [&assets_by_symbol](const string &symbol) { // auto asset_itr = assets_by_symbol.find(symbol); @@ -53,7 +52,7 @@ sidechain_net_handler_peerplays::~sidechain_net_handler_peerplays() { bool sidechain_net_handler_peerplays::process_proposal(const proposal_object &po) { - ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id())); + ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id(sidechain))); bool should_approve = false; @@ -140,7 +139,7 @@ void sidechain_net_handler_peerplays::process_sidechain_addresses() { if (sao.expires == time_point_sec::maximum()) { if (sao.deposit_address == "") { sidechain_address_update_operation op; - op.payer = plugin.get_current_son_object().son_account; + op.payer = plugin.get_current_son_object(sidechain).son_account; op.sidechain_address_id = sao.id; op.sidechain_address_account = sao.sidechain_address_account; op.sidechain = sao.sidechain; @@ -150,7 +149,7 @@ void sidechain_net_handler_peerplays::process_sidechain_addresses() { op.withdraw_public_key = sao.withdraw_public_key; op.withdraw_address = sao.withdraw_address; - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -197,15 +196,22 @@ bool sidechain_net_handler_peerplays::process_deposit(const son_wallet_deposit_o stc_op.object_id = swdo.id; stc_op.sidechain = sidechain; stc_op.transaction = tx_str; - stc_op.signers = gpo.active_sons; + for (const auto &signer : gpo.active_sons.at(sidechain)) { + son_info si; + si.son_id = signer.son_id; + si.weight = signer.weight; + si.signing_key = signer.signing_key; + si.sidechain_public_keys[sidechain] = signer.public_key; + stc_op.signers.emplace_back(std::move(si)); + } proposal_create_operation proposal_op; - proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.fee_paying_account = plugin.get_current_son_object(sidechain).son_account; proposal_op.proposed_ops.emplace_back(stc_op); uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); - signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id(sidechain)), proposal_op); try { trx.validate(); database.push_transaction(trx, database::validation_steps::skip_block_size_check); @@ -230,7 +236,7 @@ std::string sidechain_net_handler_peerplays::process_sidechain_transaction(const signed_transaction trx; fc::raw::unpack(ss_trx, trx, 1000); - fc::optional privkey = graphene::utilities::wif_to_key(get_private_key(plugin.get_current_son_object().sidechain_public_keys.at(sidechain))); + fc::optional privkey = graphene::utilities::wif_to_key(get_private_key(plugin.get_current_son_object(sidechain).sidechain_public_keys.at(sidechain))); signature_type st = trx.sign(*privkey, database.get_chain_id()); std::stringstream ss_st; @@ -290,4 +296,9 @@ bool sidechain_net_handler_peerplays::settle_sidechain_transaction(const sidecha return true; } +optional sidechain_net_handler_peerplays::estimate_withdrawal_transaction_fee() const { + wlog("estimate_withdrawal_transaction_fee not implemented for sidechain: ${sidechain}", ("sidechain", sidechain)); + return optional{}; +} + }} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp deleted file mode 100644 index e2cb1608..00000000 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp +++ /dev/null @@ -1,112 +0,0 @@ -#include - -#include -#include -#include -#include -#include - -namespace graphene { namespace peerplays_sidechain { - -sidechain_net_manager::sidechain_net_manager(peerplays_sidechain_plugin &_plugin) : - plugin(_plugin), - database(_plugin.database()) { - - //database.applied_block.connect([&](const signed_block &b) { - // on_applied_block(b); - //}); -} - -sidechain_net_manager::~sidechain_net_manager() { -} - -bool sidechain_net_manager::create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options) { - - bool ret_val = false; - - switch (sidechain) { - case sidechain_type::bitcoin: { - std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_bitcoin(plugin, options)); - net_handlers.push_back(std::move(h)); - ret_val = true; - break; - } - case sidechain_type::hive: { - std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_hive(plugin, options)); - net_handlers.push_back(std::move(h)); - ret_val = true; - break; - } - case sidechain_type::peerplays: { - std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_peerplays(plugin, options)); - net_handlers.push_back(std::move(h)); - ret_val = true; - break; - } - default: - assert(false); - } - - return ret_val; -} - -void sidechain_net_manager::process_proposals() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->process_proposals(); - } -} - -void sidechain_net_manager::process_active_sons_change() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->process_active_sons_change(); - } -} - -void sidechain_net_manager::create_deposit_addresses() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->create_deposit_addresses(); - } -} - -void sidechain_net_manager::process_deposits() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->process_deposits(); - } -} - -void sidechain_net_manager::process_withdrawals() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->process_withdrawals(); - } -} - -void sidechain_net_manager::process_sidechain_transactions() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->process_sidechain_transactions(); - } -} - -void sidechain_net_manager::send_sidechain_transactions() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->send_sidechain_transactions(); - } -} - -void sidechain_net_manager::settle_sidechain_transactions() { - for (size_t i = 0; i < net_handlers.size(); i++) { - net_handlers.at(i)->settle_sidechain_transactions(); - } -} - -std::map> sidechain_net_manager::get_son_listener_log() { - std::map> result; - for (size_t i = 0; i < net_handlers.size(); i++) { - result[net_handlers.at(i)->get_sidechain()] = net_handlers.at(i)->get_son_listener_log(); - } - return result; -} - -void sidechain_net_manager::on_applied_block(const signed_block &b) { -} - -}} // namespace graphene::peerplays_sidechain diff --git a/libraries/sha3/CMakeLists.txt b/libraries/sha3/CMakeLists.txt new file mode 100644 index 00000000..c790323e --- /dev/null +++ b/libraries/sha3/CMakeLists.txt @@ -0,0 +1,16 @@ +file(GLOB HEADERS "include/sha3/*.h") + +add_library( sha3 + memzero.c + sha3.c +) + +target_include_directories( sha3 PUBLIC ${CMAKE_CURRENT_LIST_DIR}/include ) +target_compile_definitions( sha3 PUBLIC USE_KECCAK=1 ) + +install( TARGETS + sha3 + RUNTIME DESTINATION bin + LIBRARY DESTINATION lib + ARCHIVE DESTINATION lib +) diff --git a/libraries/sha3/include/sha3/memzero.h b/libraries/sha3/include/sha3/memzero.h new file mode 100644 index 00000000..1e744c12 --- /dev/null +++ b/libraries/sha3/include/sha3/memzero.h @@ -0,0 +1,16 @@ +#ifndef __MEMZERO_H__ +#define __MEMZERO_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif +void memzero(void* const pnt, const size_t len); + +#ifdef __cplusplus +} /* extern "C" */ +#endif + + +#endif diff --git a/libraries/sha3/include/sha3/sha3.h b/libraries/sha3/include/sha3/sha3.h new file mode 100644 index 00000000..881e806c --- /dev/null +++ b/libraries/sha3/include/sha3/sha3.h @@ -0,0 +1,88 @@ +/* sha3.h - an implementation of Secure Hash Algorithm 3 (Keccak). + * based on the + * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 + * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche + * + * Copyright: 2013 Aleksey Kravchenko + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so. + * + * 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. Use this program at your own risk! + */ + +#ifndef __SHA3_H__ +#define __SHA3_H__ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +#define sha3_224_hash_size 28 +#define sha3_256_hash_size 32 +#define sha3_384_hash_size 48 +#define sha3_512_hash_size 64 +#define sha3_max_permutation_size 25 +#define sha3_max_rate_in_qwords 24 + +#define SHA3_224_BLOCK_LENGTH 144 +#define SHA3_256_BLOCK_LENGTH 136 +#define SHA3_384_BLOCK_LENGTH 104 +#define SHA3_512_BLOCK_LENGTH 72 + +#define SHA3_224_DIGEST_LENGTH sha3_224_hash_size +#define SHA3_256_DIGEST_LENGTH sha3_256_hash_size +#define SHA3_384_DIGEST_LENGTH sha3_384_hash_size +#define SHA3_512_DIGEST_LENGTH sha3_512_hash_size + +/** + * SHA3 Algorithm context. + */ +typedef struct SHA3_CTX +{ + /* 1600 bits algorithm hashing state */ + uint64_t hash[sha3_max_permutation_size]; + /* 1536-bit buffer for leftovers */ + uint64_t message[sha3_max_rate_in_qwords]; + /* count of bytes in the message[] buffer */ + unsigned rest; + /* size of a message block processed at once */ + unsigned block_size; +} SHA3_CTX; + +/* methods for calculating the hash function */ + +void sha3_224_Init(SHA3_CTX *ctx); +void sha3_256_Init(SHA3_CTX *ctx); +void sha3_384_Init(SHA3_CTX *ctx); +void sha3_512_Init(SHA3_CTX *ctx); +void sha3_Update(SHA3_CTX *ctx, const unsigned char* msg, size_t size); +void sha3_Final(SHA3_CTX *ctx, unsigned char* result); + +#if USE_KECCAK +#define keccak_224_Init sha3_224_Init +#define keccak_256_Init sha3_256_Init +#define keccak_384_Init sha3_384_Init +#define keccak_512_Init sha3_512_Init +#define keccak_Update sha3_Update +void keccak_Final(SHA3_CTX *ctx, unsigned char* result); +void keccak_256(const unsigned char* data, size_t len, unsigned char* digest); +void keccak_512(const unsigned char* data, size_t len, unsigned char* digest); +#endif + +void sha3_256(const unsigned char* data, size_t len, unsigned char* digest); +void sha3_512(const unsigned char* data, size_t len, unsigned char* digest); + +#ifdef __cplusplus +} /* extern "C" */ +#endif /* __cplusplus */ + +#endif /* __SHA3_H__ */ diff --git a/libraries/sha3/memzero.c b/libraries/sha3/memzero.c new file mode 100644 index 00000000..32aa140f --- /dev/null +++ b/libraries/sha3/memzero.c @@ -0,0 +1,75 @@ +#ifndef __STDC_WANT_LIB_EXT1__ +#define __STDC_WANT_LIB_EXT1__ 1 // C11's bounds-checking interface. +#endif +#include + +#ifdef _WIN32 +#include +#endif + +#ifdef __unix__ +#include +#include +#endif + +// C11's bounds-checking interface. +#if defined(__STDC_LIB_EXT1__) +#define HAVE_MEMSET_S 1 +#endif + +// GNU C Library version 2.25 or later. +#if defined(__GLIBC__) && \ + (__GLIBC__ > 2 || (__GLIBC__ == 2 && __GLIBC_MINOR__ >= 25)) +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// Newlib +#if defined(__NEWLIB__) +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// FreeBSD version 11.0 or later. +#if defined(__FreeBSD__) && __FreeBSD_version >= 1100037 +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// OpenBSD version 5.5 or later. +#if defined(__OpenBSD__) && OpenBSD >= 201405 +#define HAVE_EXPLICIT_BZERO 1 +#endif + +// NetBSD version 7.2 or later. +#if defined(__NetBSD__) && __NetBSD_Version__ >= 702000000 +#define HAVE_EXPLICIT_MEMSET 1 +#endif + +// Adapted from +// https://github.com/jedisct1/libsodium/blob/1647f0d53ae0e370378a9195477e3df0a792408f/src/libsodium/sodium/utils.c#L102-L130 + +void memzero(void *const pnt, const size_t len) { +#ifdef _WIN32 + SecureZeroMemory(pnt, len); +#elif defined(HAVE_MEMSET_S) + memset_s(pnt, (rsize_t)len, 0, (rsize_t)len); +#elif defined(HAVE_EXPLICIT_BZERO) + explicit_bzero(pnt, len); +#elif defined(HAVE_EXPLICIT_MEMSET) + explicit_memset(pnt, 0, len); +#else + volatile unsigned char *volatile pnt_ = (volatile unsigned char *volatile)pnt; + size_t i = (size_t)0U; + + while (i < len) { + pnt_[i++] = 0U; + } +#endif + + // explicitly mark the memory as overwritten for the Clang MemorySanitizer + // this is only included at compile time if MemorySanitizer is enabled and + // should not come with any downsides during regular builds +#if defined(__has_feature) +#if __has_feature(memory_sanitizer) + memset(pnt, 0, len); +#endif +#endif +} diff --git a/libraries/sha3/sha3.c b/libraries/sha3/sha3.c new file mode 100644 index 00000000..172728eb --- /dev/null +++ b/libraries/sha3/sha3.c @@ -0,0 +1,397 @@ +/* sha3.c - an implementation of Secure Hash Algorithm 3 (Keccak). + * based on the + * The Keccak SHA-3 submission. Submission to NIST (Round 3), 2011 + * by Guido Bertoni, Joan Daemen, Michaël Peeters and Gilles Van Assche + * + * Copyright: 2013 Aleksey Kravchenko + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so. + * + * 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. Use this program at your own risk! + */ + +#include +#include + +#include +#include + +#define I64(x) x##LL +#define ROTL64(qword, n) ((qword) << (n) ^ ((qword) >> (64 - (n)))) +#define le2me_64(x) (x) +#define IS_ALIGNED_64(p) (0 == (7 & ((long)(p)))) // [wallet-core] pointer/numerical type, for MacOS SDK 12.3 +# define me64_to_le_str(to, from, length) memcpy((to), (from), (length)) + +/* constants */ +#define NumberOfRounds 24 + +/* SHA3 (Keccak) constants for 24 rounds */ +uint64_t keccak_round_constants[NumberOfRounds] = { + I64(0x0000000000000001), I64(0x0000000000008082), I64(0x800000000000808A), I64(0x8000000080008000), + I64(0x000000000000808B), I64(0x0000000080000001), I64(0x8000000080008081), I64(0x8000000000008009), + I64(0x000000000000008A), I64(0x0000000000000088), I64(0x0000000080008009), I64(0x000000008000000A), + I64(0x000000008000808B), I64(0x800000000000008B), I64(0x8000000000008089), I64(0x8000000000008003), + I64(0x8000000000008002), I64(0x8000000000000080), I64(0x000000000000800A), I64(0x800000008000000A), + I64(0x8000000080008081), I64(0x8000000000008080), I64(0x0000000080000001), I64(0x8000000080008008) +}; + +/* Initializing a sha3 context for given number of output bits */ +static void keccak_Init(SHA3_CTX *ctx, unsigned bits) +{ + /* NB: The Keccak capacity parameter = bits * 2 */ + unsigned rate = 1600 - bits * 2; + + memzero(ctx, sizeof(SHA3_CTX)); + ctx->block_size = rate / 8; + assert(rate <= 1600 && (rate % 64) == 0); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_224_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 224); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_256_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 256); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_384_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 384); +} + +/** + * Initialize context before calculating hash. + * + * @param ctx context to initialize + */ +void sha3_512_Init(SHA3_CTX *ctx) +{ + keccak_Init(ctx, 512); +} + +/* Keccak theta() transformation */ +static void keccak_theta(uint64_t *A) +{ + unsigned int x = 0; + uint64_t C[5] = {0}, D[5] = {0}; + + for (x = 0; x < 5; x++) { + C[x] = A[x] ^ A[x + 5] ^ A[x + 10] ^ A[x + 15] ^ A[x + 20]; + } + D[0] = ROTL64(C[1], 1) ^ C[4]; + D[1] = ROTL64(C[2], 1) ^ C[0]; + D[2] = ROTL64(C[3], 1) ^ C[1]; + D[3] = ROTL64(C[4], 1) ^ C[2]; + D[4] = ROTL64(C[0], 1) ^ C[3]; + + for (x = 0; x < 5; x++) { + A[x] ^= D[x]; + A[x + 5] ^= D[x]; + A[x + 10] ^= D[x]; + A[x + 15] ^= D[x]; + A[x + 20] ^= D[x]; + } +} + +/* Keccak pi() transformation */ +static void keccak_pi(uint64_t *A) +{ + uint64_t A1 = 0; + A1 = A[1]; + A[ 1] = A[ 6]; + A[ 6] = A[ 9]; + A[ 9] = A[22]; + A[22] = A[14]; + A[14] = A[20]; + A[20] = A[ 2]; + A[ 2] = A[12]; + A[12] = A[13]; + A[13] = A[19]; + A[19] = A[23]; + A[23] = A[15]; + A[15] = A[ 4]; + A[ 4] = A[24]; + A[24] = A[21]; + A[21] = A[ 8]; + A[ 8] = A[16]; + A[16] = A[ 5]; + A[ 5] = A[ 3]; + A[ 3] = A[18]; + A[18] = A[17]; + A[17] = A[11]; + A[11] = A[ 7]; + A[ 7] = A[10]; + A[10] = A1; + /* note: A[ 0] is left as is */ +} + +/* Keccak chi() transformation */ +static void keccak_chi(uint64_t *A) +{ + int i = 0; + for (i = 0; i < 25; i += 5) { + uint64_t A0 = A[0 + i], A1 = A[1 + i]; + A[0 + i] ^= ~A1 & A[2 + i]; + A[1 + i] ^= ~A[2 + i] & A[3 + i]; + A[2 + i] ^= ~A[3 + i] & A[4 + i]; + A[3 + i] ^= ~A[4 + i] & A0; + A[4 + i] ^= ~A0 & A1; + } +} + +static void sha3_permutation(uint64_t *state) +{ + int round = 0; + for (round = 0; round < NumberOfRounds; round++) + { + keccak_theta(state); + + /* apply Keccak rho() transformation */ + state[ 1] = ROTL64(state[ 1], 1); + state[ 2] = ROTL64(state[ 2], 62); + state[ 3] = ROTL64(state[ 3], 28); + state[ 4] = ROTL64(state[ 4], 27); + state[ 5] = ROTL64(state[ 5], 36); + state[ 6] = ROTL64(state[ 6], 44); + state[ 7] = ROTL64(state[ 7], 6); + state[ 8] = ROTL64(state[ 8], 55); + state[ 9] = ROTL64(state[ 9], 20); + state[10] = ROTL64(state[10], 3); + state[11] = ROTL64(state[11], 10); + state[12] = ROTL64(state[12], 43); + state[13] = ROTL64(state[13], 25); + state[14] = ROTL64(state[14], 39); + state[15] = ROTL64(state[15], 41); + state[16] = ROTL64(state[16], 45); + state[17] = ROTL64(state[17], 15); + state[18] = ROTL64(state[18], 21); + state[19] = ROTL64(state[19], 8); + state[20] = ROTL64(state[20], 18); + state[21] = ROTL64(state[21], 2); + state[22] = ROTL64(state[22], 61); + state[23] = ROTL64(state[23], 56); + state[24] = ROTL64(state[24], 14); + + keccak_pi(state); + keccak_chi(state); + + /* apply iota(state, round) */ + *state ^= keccak_round_constants[round]; + } +} + +/** + * The core transformation. Process the specified block of data. + * + * @param hash the algorithm state + * @param block the message block to process + * @param block_size the size of the processed block in bytes + */ +static void sha3_process_block(uint64_t hash[25], const uint64_t *block, size_t block_size) +{ + /* expanded loop */ + hash[ 0] ^= le2me_64(block[ 0]); + hash[ 1] ^= le2me_64(block[ 1]); + hash[ 2] ^= le2me_64(block[ 2]); + hash[ 3] ^= le2me_64(block[ 3]); + hash[ 4] ^= le2me_64(block[ 4]); + hash[ 5] ^= le2me_64(block[ 5]); + hash[ 6] ^= le2me_64(block[ 6]); + hash[ 7] ^= le2me_64(block[ 7]); + hash[ 8] ^= le2me_64(block[ 8]); + /* if not sha3-512 */ + if (block_size > 72) { + hash[ 9] ^= le2me_64(block[ 9]); + hash[10] ^= le2me_64(block[10]); + hash[11] ^= le2me_64(block[11]); + hash[12] ^= le2me_64(block[12]); + /* if not sha3-384 */ + if (block_size > 104) { + hash[13] ^= le2me_64(block[13]); + hash[14] ^= le2me_64(block[14]); + hash[15] ^= le2me_64(block[15]); + hash[16] ^= le2me_64(block[16]); + /* if not sha3-256 */ + if (block_size > 136) { + hash[17] ^= le2me_64(block[17]); +#ifdef FULL_SHA3_FAMILY_SUPPORT + /* if not sha3-224 */ + if (block_size > 144) { + hash[18] ^= le2me_64(block[18]); + hash[19] ^= le2me_64(block[19]); + hash[20] ^= le2me_64(block[20]); + hash[21] ^= le2me_64(block[21]); + hash[22] ^= le2me_64(block[22]); + hash[23] ^= le2me_64(block[23]); + hash[24] ^= le2me_64(block[24]); + } +#endif + } + } + } + /* make a permutation of the hash */ + sha3_permutation(hash); +} + +#define SHA3_FINALIZED 0x80000000 + +/** + * Calculate message hash. + * Can be called repeatedly with chunks of the message to be hashed. + * + * @param ctx the algorithm context containing current hashing state + * @param msg message chunk + * @param size length of the message chunk + */ +void sha3_Update(SHA3_CTX *ctx, const unsigned char *msg, size_t size) +{ + size_t idx = (size_t)ctx->rest; + size_t block_size = (size_t)ctx->block_size; + + if (ctx->rest & SHA3_FINALIZED) return; /* too late for additional input */ + ctx->rest = (unsigned)((ctx->rest + size) % block_size); + + /* fill partial block */ + if (idx) { + size_t left = block_size - idx; + memcpy((char*)ctx->message + idx, msg, (size < left ? size : left)); + if (size < left) return; + + /* process partial block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + msg += left; + size -= left; + } + while (size >= block_size) { + uint64_t *aligned_message_block = NULL; + if (IS_ALIGNED_64(msg)) { + /* the most common case is processing of an already aligned message + without copying it */ + aligned_message_block = (uint64_t*)(void*)msg; + } else { + memcpy(ctx->message, msg, block_size); + aligned_message_block = ctx->message; + } + + sha3_process_block(ctx->hash, aligned_message_block, block_size); + msg += block_size; + size -= block_size; + } + if (size) { + memcpy(ctx->message, msg, size); /* save leftovers */ + } +} + +/** + * Store calculated hash into the given array. + * + * @param ctx the algorithm context containing current hashing state + * @param result calculated hash in binary form + */ +void sha3_Final(SHA3_CTX *ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x06; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); + memzero(ctx, sizeof(SHA3_CTX)); +} + +#if USE_KECCAK +/** +* Store calculated hash into the given array. +* +* @param ctx the algorithm context containing current hashing state +* @param result calculated hash in binary form +*/ +void keccak_Final(SHA3_CTX *ctx, unsigned char* result) +{ + size_t digest_length = 100 - ctx->block_size / 2; + const size_t block_size = ctx->block_size; + + if (!(ctx->rest & SHA3_FINALIZED)) + { + /* clear the rest of the data queue */ + memzero((char*)ctx->message + ctx->rest, block_size - ctx->rest); + ((char*)ctx->message)[ctx->rest] |= 0x01; + ((char*)ctx->message)[block_size - 1] |= 0x80; + + /* process final block */ + sha3_process_block(ctx->hash, ctx->message, block_size); + ctx->rest = SHA3_FINALIZED; /* mark context as finalized */ + } + + assert(block_size > digest_length); + if (result) me64_to_le_str(result, ctx->hash, digest_length); + memzero(ctx, sizeof(SHA3_CTX)); +} + +void keccak_256(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + keccak_256_Init(&ctx); + keccak_Update(&ctx, data, len); + keccak_Final(&ctx, digest); +} + +void keccak_512(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + keccak_512_Init(&ctx); + keccak_Update(&ctx, data, len); + keccak_Final(&ctx, digest); +} +#endif /* USE_KECCAK */ + +void sha3_256(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + sha3_256_Init(&ctx); + sha3_Update(&ctx, data, len); + sha3_Final(&ctx, digest); +} + +void sha3_512(const unsigned char* data, size_t len, unsigned char* digest) +{ + SHA3_CTX ctx = {0}; + sha3_512_Init(&ctx); + sha3_Update(&ctx, data, len); + sha3_Final(&ctx, digest); +} diff --git a/libraries/wallet/generate_api_documentation.pl b/libraries/wallet/generate_api_documentation.pl index a3f333db..c1a8bb40 100755 --- a/libraries/wallet/generate_api_documentation.pl +++ b/libraries/wallet/generate_api_documentation.pl @@ -44,13 +44,37 @@ for my $class (@{$doxydocs->{classes}}) if ($member->{kind} eq 'function') { my @params = map { join(' ', cleanupDoxygenType($_->{type}), $_->{declaration_name}) } @{$member->{parameters}}; - my $briefDescription = sprintf("%40s %s(%s)\n", cleanupDoxygenType($member->{type}), $member->{name}, join(', ', @params)); - my $escapedBriefDescription = "\"" . escapeStringForC($briefDescription) . "\""; - my %paramInfo = map { $_->{declaration_name} => { type => $_->{type}} } @{$member->{parameters}}; + my $callDescription = sprintf("%40s %s(%s)\n", cleanupDoxygenType($member->{type}), $member->{name}, join(', ', @params)); + my $escapedBriefDescription = "\"" . escapeStringForC($callDescription) . "\""; + my %paramInfo = map { $_->{declaration_name} => { type => explainCType(cleanupDoxygenType($_->{type})) } } @{$member->{parameters}}; my $escapedDetailedDescription = "\"\"\n"; - if ($member->{detailed}->{doc}) + my $doc = $member->{detailed}->{doc}; + if ($doc) { - my $docString = formatDocComment($member->{detailed}->{doc}, \%paramInfo); + my $briefDescr = formatDocComment($member->{brief}->{doc}); # get from the proper place + unless ($briefDescr =~ /\w/) # if not provided (API author forgot to add '@brief' comment), + { + for (my $i = 0; $i < @{$doc}; ++$i) # then look inside 'detailed' section + { + my $docElement = $doc->[$i]; + if ($docElement->{type} eq 'text' and $docElement->{content} =~ /\w+/) # use first meaningful line as brief description + { + $briefDescr = $docElement->{content}; + $briefDescr =~ s/^\s+|\s+$//g; + splice @{$doc}, $i, 1; # this section shouldn't be used twice + last; + } + } + } + + my $cmdSyntax = $member->{name}; + my $cmdArgs = join(' ', map { $_->{declaration_name} } @{$member->{parameters}}); + $cmdSyntax .= " $cmdArgs" if $cmdArgs; + + my $docString; + $docString .= $briefDescr; + $docString .= "\n\n" . formatDocComment($doc, \%paramInfo, $cmdSyntax); + for my $line (split(/\n/, $docString)) { $escapedDetailedDescription .= " \"" . escapeStringForC($line . "\n") . "\"\n"; @@ -96,62 +120,86 @@ sub cleanupDoxygenType return $type; } +sub explainCType +{ + my($type) = @_; + $type =~ s/\b\w+:://g; # remove namespaces + $type =~ s/^(?:optional|api)<(.+)>$/$1/; # disregard optional<> and some other templates + $type =~ s/^const\s+(.+)/$1/; # strip const modifier + $type =~ s/^(.+)&/$1/; # strip references + $type =~ s/\s+$/$1/; + $type =~ s/\b(u?int(8|16|32|64)_t|int|unsigned)\b/integer/; # spare the user from width and signedness + $type =~ s/\bbool\b/boolean/; # they're not C++ people + $type =~ s/^(?:vector|set|flat_set)<(.+)>$/[$1, ...]/; # represent as JSon-like array notation + $type =~ s/^map<(.+)\s*,\s*(.+)>$/{$1: $2, ...}/; # same for map + $type =~ s/^flat_map<(.+)\s*,\s*(.+)>$/[[$1, $2], ...]/; # same for flat_map + $type =~ s/^time_point_sec$/time, e.g. 2021-12-25T14:30:05/; + return $type; +} + sub formatDocComment { - my($doc, $paramInfo) = @_; + my($doc, $paramInfo, $cmdSyntax) = @_; my $bodyDocs = ''; + my $notes = ''; + my $see = ''; my $paramDocs = ''; my $returnDocs = ''; for (my $i = 0; $i < @{$doc}; ++$i) { - if ($doc->[$i] eq 'params') + my $docElement = $doc->[$i]; + + if ($docElement->{params}) { $paramDocs .= "Parameters:\n"; - @parametersList = @{$doc->[$i + 1]}; - for my $parameter (@parametersList) + for my $parameter (@{$docElement->{params}}) { my $declname = $parameter->{parameters}->[0]->{name}; my $decltype = cleanupDoxygenType($paramInfo->{$declname}->{type}); - $paramDocs .= Text::Wrap::fill(' ', ' ', "$declname: " . formatDocComment($parameter->{doc}) . " (type: $decltype)") . "\n"; + $paramDocs .= Text::Wrap::fill(' ', ' ', "$declname ($decltype): " . formatDocComment($parameter->{doc})) . "\n"; } - ++$i; } - elsif ($doc->[$i]->{return}) + elsif ($docElement->{return}) { - $returnDocs .= "Returns\n"; - $returnDocs .= Text::Wrap::fill(' ',' ', formatDocComment($doc->[$i]->{return})) . "\n"; + $returnDocs .= "Returns:\n"; + $returnDocs .= Text::Wrap::fill(' ',' ', formatDocComment($docElement->{return})) . "\n"; } - else + elsif ($docElement->{note}) { - my $docElement = $doc->[$i]; - if ($docElement->{type} eq 'text' or $docElement->{type} eq 'url') - { - $bodyDocs .= $docElement->{content}; - } - elsif ($docElement->{type} eq 'parbreak') - { - $bodyDocs .= "\n\n"; - } - elsif ($docElement->{type} eq 'style' and $docElement->{style} eq 'code') - { - $bodyDocs .= "'"; - } + $notes .= Text::Wrap::fill(' ',' ', "Note: ".formatDocComment($docElement->{note})) . "\n"; + } + elsif ($docElement->{see}) + { + $see .= Text::Wrap::fill(' ',' ', "See: ".formatDocComment($docElement->{see})) . "\n"; + } + elsif ($docElement->{type} eq 'text' or $docElement->{type} eq 'url') + { + $bodyDocs .= $docElement->{content}; + } + elsif ($docElement->{type} eq 'parbreak') + { + $bodyDocs .= "\n\n"; + } + elsif ($docElement->{type} eq 'style' and $docElement->{style} eq 'code') + { + $bodyDocs .= "'"; } } $bodyDocs =~ s/^\s+|\s+$//g; $bodyDocs = Text::Wrap::fill('', '', $bodyDocs); - $paramDocs =~ s/^\s+|\s+$//g; + $notes =~ s/^\s+|\s+$//g; + $see =~ s/^\s+|\s+$//g; + $paramDocs =~ s/^\s+|\s+$//g; $returnDocs =~ s/^\s+|\s+$//g; - my $result = Text::Wrap::fill('', '', $bodyDocs); - $result .= "\n\n" . $paramDocs if $paramDocs; - $result .= "\n\n" . $returnDocs if $returnDocs; - - return $result; + my $cmdDocs; + $cmdDocs = "Command:\n" . Text::Wrap::fill(' ',' ', $cmdSyntax) if $cmdSyntax; + + return join "\n\n", grep {$_} ($bodyDocs, $notes, $see, $cmdDocs, $paramDocs, $returnDocs); } sub escapeCharForCString diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 02790e19..1de38ffa 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -68,80 +68,12 @@ enum authority_type active }; -/** - * Contains the confirmation receipt the sender must give the receiver and - * the meta data about the receipt that helps the sender identify which receipt is - * for the receiver and which is for the change address. - */ -struct blind_confirmation -{ - struct output - { - string label; - public_key_type pub_key; - stealth_confirmation::memo_data decrypted_memo; - stealth_confirmation confirmation; - authority auth; - string confirmation_receipt; - }; - - signed_transaction trx; - vector outputs; -}; - -struct blind_balance -{ - asset amount; - public_key_type from; ///< the account this balance came from - public_key_type to; ///< the account this balance is logically associated with - public_key_type one_time_key; ///< used to derive the authority key and blinding factor - fc::sha256 blinding_factor; - fc::ecc::commitment_type commitment; - bool used = false; -}; - -struct blind_receipt -{ - std::pair from_date()const { return std::make_pair(from_key,date); } - std::pair to_date()const { return std::make_pair(to_key,date); } - std::tuple to_asset_used()const { return std::make_tuple(to_key,amount.asset_id,used); } - const commitment_type& commitment()const { return data.commitment; } - - fc::time_point date; - public_key_type from_key; - string from_label; - public_key_type to_key; - string to_label; - asset amount; - string memo; - authority control_authority; - stealth_confirmation::memo_data data; - bool used = false; - stealth_confirmation conf; -}; - -struct by_from; -struct by_to; -struct by_to_asset_used; -struct by_commitment; - -typedef multi_index_container< blind_receipt, - indexed_by< - ordered_unique< tag, const_mem_fun< blind_receipt, const commitment_type&, &blind_receipt::commitment > >, - ordered_unique< tag, const_mem_fun< blind_receipt, std::pair, &blind_receipt::to_date > >, - ordered_non_unique< tag, const_mem_fun< blind_receipt, std::tuple, &blind_receipt::to_asset_used > >, - ordered_unique< tag, const_mem_fun< blind_receipt, std::pair, &blind_receipt::from_date > > - > -> blind_receipt_index_type; - - struct key_label { string label; public_key_type key; }; - struct by_label; struct by_key; typedef multi_index_container< @@ -152,6 +84,32 @@ typedef multi_index_container< > > key_label_index_type; +/* How to write doxygen docs + * + * Good + * / ** Returns the block chain's rapidly-changing properties. + * * + * * The returned object contains information that changes every block interval + * * such as the head block number, the next witness, etc. + * * / + * + * Bad, no empty line + * / ** Returns the block chain's rapidly-changing properties. + * * The returned object contains information that changes every block interval + * * such as the head block number, the next witness, etc. + * * / + * + * Better, using @brief tag + * / ** + * * @brief Returns the block chain's rapidly-changing properties. + * * Long description text 1 + * * Long description text 2 + * * @param param1 param1 description + * * @param param2 param2 description + * * @returns return value description + * * / + * string get_rapidly_changing_properties(int32_t interval, string chain_id) + */ struct wallet_data { @@ -195,7 +153,6 @@ struct wallet_data map pending_witness_registrations; key_label_index_type labeled_keys; - blind_receipt_index_type blind_receipts; std::map committed_game_moves; @@ -234,17 +191,6 @@ struct worker_vote_delta flat_set vote_abstain; }; -struct signed_block_with_info : public signed_block -{ - signed_block_with_info(); - signed_block_with_info( const signed_block& block ); - signed_block_with_info( const signed_block_with_info& block ) = default; - - block_id_type block_id; - public_key_type signing_key; - vector< transaction_id_type > transaction_ids; -}; - struct vesting_balance_object_with_info : public vesting_balance_object { vesting_balance_object_with_info(); @@ -306,18 +252,37 @@ class wallet_api fc::ecc::private_key derive_private_key(const std::string& prefix_string, int sequence_number) const; + /** Returns info about head block, chain_id, maintenance, participation, current active witnesses and + * committee members. + * @returns runtime info about the blockchain + */ variant info(); /** Returns info such as client version, git version of graphene/fc, version of boost, openssl. * @returns compile time info and client and dependencies versions */ variant_object about() const; - optional get_block( uint32_t num ); + /** Returns info about a specified block. + * @param num height of the block to retrieve + * @returns info about the block, or null if not found + */ + optional get_block( uint32_t num ); + /** Returns info about a specified block, with some extra info. + * @param num height of the block to retrieve + * @returns info about the block, or null if not found + */ + optional get_block2( uint32_t num ); + /** Get signed blocks + * @param block_num_from The lowest block number + * @param block_num_to The highest block number + * @returns A list of signed blocks from block_num_from till block_num_to + */ vector> get_blocks(uint32_t block_num_from, uint32_t block_num_to)const; /** Returns the number of accounts registered on the blockchain * @returns the number of registered accounts */ uint64_t get_account_count()const; /** Lists all accounts controlled by this wallet. + * * This returns a list of the full account objects for all accounts whose private keys * we possess. * @returns a list of account objects @@ -393,12 +358,42 @@ class wallet_api vector list_core_accounts()const; + /** Get OHLCV data of a trading pair in a time range + * @param symbol Asset symbol or ID in a trading pair + * @param symbol2 The other asset symbol or ID in the trading pair + * @param bucket Length of each time bucket in seconds. + * Note: it need to be within result of get_market_history_buckets() API, otherwise no data will be returned + * @param start The start of a time range, E.G. "2018-01-01T00:00:00" + * @param end The end of the time range + * @return A list of OHLCV data, in "least recent first" order. + * If there are more than 200 records in the specified time range, the first 200 records will be returned. + */ vector get_market_history(string symbol, string symbol2, uint32_t bucket, fc::time_point_sec start, fc::time_point_sec end)const; + + /** Get limit orders in a given market + * @param a symbol or ID of asset being sold + * @param b symbol or ID of asset being purchased + * @param limit Maximum number of orders to retrieve + * @return The limit orders, ordered from least price to greatest + */ vector get_limit_orders(string a, string b, uint32_t limit)const; + + /** Get call orders (aka margin positions) for a given asset + * @param a symbol or ID of the debt asset + * @param limit Maximum number of orders to retrieve + * @return The call orders, ordered from earliest to be called to latest + */ vector get_call_orders(string a, uint32_t limit)const; + + /** Get forced settlement orders in a given asset + * @param a Symbol or ID of asset being settled + * @param limit Maximum number of orders to retrieve + * @return The settle orders, ordered from earliest settlement date to latest + */ vector get_settle_orders(string a, uint32_t limit)const; /** Returns the block chain's slowly-changing settings. + * * This object contains all of the properties of the blockchain that are fixed * or that change only once per maintenance interval (daily) such as the * current list of witnesses, committee_members, block interval, etc. @@ -408,6 +403,7 @@ class wallet_api global_property_object get_global_properties() const; /** Returns the block chain's rapidly-changing properties. + * * The returned object contains information that changes every block interval * such as the head block number, the next witness, etc. * @see \c get_global_properties() for less-frequently changing properties @@ -423,6 +419,7 @@ class wallet_api account_object get_account(string account_name_or_id) const; /** Returns information about the given asset. + * * @param asset_name_or_id the symbol or id of the asset in question * @returns the information about the asset stored in the block chain */ @@ -437,6 +434,7 @@ class wallet_api asset_bitasset_data_object get_bitasset_data(string asset_name_or_id)const; /** Lookup the id of a named account. + * * @param account_name_or_id the name of the account to look up * @returns the id of the named account */ @@ -444,6 +442,7 @@ class wallet_api /** * Lookup the id of a named asset. + * * @param asset_name_or_id the symbol of an asset to look up * @returns the id of the given asset */ @@ -474,42 +473,90 @@ class wallet_api /** * Get the WIF private key corresponding to a public key. The * private key must already be in the wallet. + * @param pubkey a public key in Base58 format + * @return the WIF private key */ string get_private_key( public_key_type pubkey )const; /** * @ingroup Transaction Builder API + * + * Create a new transaction builder. + * @return handle of the new transaction builder */ transaction_handle_type begin_builder_transaction(); + /** * @ingroup Transaction Builder API + * + * Append a new operation to a transaction builder. + * @param transaction_handle handle of the transaction builder + * @param op the operation in JSON format */ void add_operation_to_builder_transaction(transaction_handle_type transaction_handle, const operation& op); + /** * @ingroup Transaction Builder API + * + * Replace an operation in a transaction builder with a new operation. + * @param handle handle of the transaction builder + * @param operation_index the index of the old operation in the builder to be replaced + * @param new_op the new operation in JSON format */ void replace_operation_in_builder_transaction(transaction_handle_type handle, unsigned operation_index, const operation& new_op); + /** * @ingroup Transaction Builder API + * + * Calculate and update fees for the operations in a transaction builder. + * @param handle handle of the transaction builder + * @param fee_asset symbol or ID of an asset that to be used to pay fees + * @return total fees */ asset set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset = GRAPHENE_SYMBOL); + /** * @ingroup Transaction Builder API + * + * Show content of a transaction builder. + * @param handle handle of the transaction builder + * @return a transaction */ transaction preview_builder_transaction(transaction_handle_type handle); + /** * @ingroup Transaction Builder API + * + * Sign the transaction in a transaction builder and optionally broadcast to the network. + * @param transaction_handle handle of the transaction builder + * @param broadcast whether to broadcast the signed transaction to the network + * @return a signed transaction */ signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true); + /** Broadcast signed transaction * @param tx signed transaction * @returns the transaction ID along with the signed transaction. */ pair broadcast_transaction(signed_transaction tx); + /** * @ingroup Transaction Builder API + * + * Create a proposal containing the operations in a transaction builder (create a new proposal_create + * operation, then replace the transaction builder with the new operation), then sign the transaction + * and optionally broadcast to the network. + * + * Note: this command is buggy because unable to specify proposer. It will be deprecated in a future release. + * Please use \c propose_builder_transaction2() instead. + * + * @param handle handle of the transaction builder + * @param expiration when the proposal will expire + * @param review_period_seconds review period of the proposal in seconds + * @param broadcast whether to broadcast the signed transaction to the network + * @return a signed transaction */ signed_transaction propose_builder_transaction( transaction_handle_type handle, @@ -518,6 +565,20 @@ class wallet_api bool broadcast = true ); + /** + * @ingroup Transaction Builder API + * + * Create a proposal containing the operations in a transaction builder (create a new proposal_create + * operation, then replace the transaction builder with the new operation), then sign the transaction + * and optionally broadcast to the network. + * + * @param handle handle of the transaction builder + * @param account_name_or_id name or ID of the account who would pay fees for creating the proposal + * @param expiration when the proposal will expire + * @param review_period_seconds review period of the proposal in seconds + * @param broadcast whether to broadcast the signed transaction to the network + * @return a signed transaction + */ signed_transaction propose_builder_transaction2( transaction_handle_type handle, string account_name_or_id, @@ -528,6 +589,9 @@ class wallet_api /** * @ingroup Transaction Builder API + * + * Destroy a transaction builder. + * @param handle handle of the transaction builder */ void remove_builder_transaction(transaction_handle_type handle); @@ -565,6 +629,11 @@ class wallet_api * * The wallet must be either 'new' or 'unlocked' to * execute this command. + * + * When used in command line, if typed "set_password" without a password followed, the user will be prompted + * to input a password without echo. + * + * @param password a new password * @ingroup Wallet Management */ void set_password(string password); @@ -646,7 +715,7 @@ class wallet_api /** * Derive any number of *possible* owner keys from a given brain key. * - * NOTE: These keys may or may not match with the owner keys of any account. + * @note These keys may or may not match with the owner keys of any account. * This function is merely intended to assist with account or key recovery. * * @see suggest_brain_key() @@ -660,7 +729,8 @@ class wallet_api /** * Determine whether a textual representation of a public key * (in Base-58 format) is *currently* linked - * to any *registered* (i.e. non-stealth) account on the blockchain + * to any *registered* (i.e. non-stealth) account on the blockchain. + * * @param public_key Public key * @return Whether a public key is known */ @@ -698,13 +768,35 @@ class wallet_api */ bool import_key(string account_name_or_id, string wif_key); + /** Imports accounts from Peerplays 0.x wallet file. + * Current wallet file must be unlocked to perform the import. + * + * @param filename the Peerplays 0.x wallet file to import + * @param password the password to encrypt the Peerplays 0.x wallet file + * @returns a map containing the accounts found and whether imported + */ map import_accounts( string filename, string password ); + /** Imports from a Peerplays 0.x wallet file, find keys that were bound to a given account name on the + * Peerplays 0.x chain, rebind them to an account name on the 2.0 chain. + * Current wallet file must be unlocked to perform the import. + * + * @param filename the Peerplays 0.x wallet file to import + * @param password the password to encrypt the Peerplays 0.x wallet file + * @param src_account_name name of the account on Peerplays 0.x chain + * @param dest_account_name name of the account on Peerplays 2.0 chain, + * can be same or different to \c src_account_name + * @returns whether the import has succeeded + */ bool import_account_keys( string filename, string password, string src_account_name, string dest_account_name ); /** * This call will construct transaction(s) that will claim all balances controled * by wif_keys and deposit them into the given account. + * + * @param account_name_or_id name or ID of an account that to claim balances to + * @param wif_keys private WIF keys of balance objects to claim balances from + * @param broadcast true to broadcast the transaction on the network */ vector< signed_transaction > import_balance( string account_name_or_id, const vector& wif_keys, bool broadcast ); @@ -751,7 +843,7 @@ class wallet_api uint32_t referrer_percent, bool broadcast = false); - /** Updates account public keys + /** Updates account public keys. * * @param name the name of the existing account * @param old_owner the owner key for the named account to be replaced @@ -769,7 +861,8 @@ class wallet_api bool broadcast = false); /** - * This method updates the key of an authority for an exisiting account. + * Updates the key of an authority for an exisiting account. + * Warning: You can create impossible authorities using this method. The method * will fail if you create an impossible owner authority, but will allow impossible * active and posting authorities. @@ -788,6 +881,7 @@ class wallet_api /** * Upgrades an account to prime status. + * * This makes the account holder a 'lifetime member'. * * @todo there is no option for annual membership @@ -842,7 +936,16 @@ class wallet_api /** * This method works just like transfer, except it always broadcasts and - * returns the transaction ID along with the signed transaction. + * returns the transaction ID (hash) along with the signed transaction. + * @param from the name or id of the account sending the funds + * @param to the name or id of the account receiving the funds + * @param amount the amount to send (in nominal units -- to send half of a BTS, specify 0.5) + * @param asset_symbol the symbol or id of the asset to send + * @param memo a memo to attach to the transaction. The memo will be encrypted in the + * transaction and readable for the receiver. There is no length limit + * other than the limit imposed by maximum transaction size, but transaction + * increase with transaction size + * @returns the transaction ID (hash) along with the signed transaction transferring funds */ pair transfer2(string from, string to, @@ -855,7 +958,9 @@ class wallet_api /** - * This method is used to convert a JSON transaction to its transactin ID. + * This method is used to convert a JSON transaction to its transactin ID. + * @param trx a JSON transaction + * @return the ID (hash) of the transaction */ transaction_id_type get_transaction_id( const signed_transaction& trx )const { return trx.id(); } @@ -863,83 +968,27 @@ class wallet_api /** These methods are used for stealth transfers */ ///@{ /** - * This method can be used to set the label for a public key + * Set the label for a public key * * @note No two keys can have the same label. * * @return true if the label was set, otherwise false */ - bool set_key_label( public_key_type, string label ); - string get_key_label( public_key_type )const; + bool set_key_label( public_key_type key, string label ); - /** - * Generates a new blind account for the given brain key and assigns it the given label. + /** Get label of a public key. + * @param key a public key + * @return the label if already set by \c set_key_label(), or an empty string if not set */ - public_key_type create_blind_account( string label, string brain_key ); + string get_key_label( public_key_type key )const; - /** - * @return the total balance of all blinded commitments that can be claimed by the - * given account key or label + /* Get the public key associated with a given label. + * @param label a label + * @return the public key associated with the given label */ - vector get_blind_balances( string key_or_label ); - /** @return all blind accounts */ - map get_blind_accounts()const; - /** @return all blind accounts for which this wallet has the private key */ - map get_my_blind_accounts()const; - /** @return the public key associated with the given label */ public_key_type get_public_key( string label )const; ///@} - /** - * @return all blind receipts to/form a particular account - */ - vector blind_history( string key_or_account ); - - /** - * Given a confirmation receipt, this method will parse it for a blinded balance and confirm - * that it exists in the blockchain. If it exists then it will report the amount received and - * who sent it. - * - * @param confirmation_receipt - a base58 encoded stealth confirmation - * @param opt_from - if not empty and the sender is a unknown public key, then the unknown public key will be given the label opt_from - * @param opt_memo - optional memo - */ - blind_receipt receive_blind_transfer( string confirmation_receipt, string opt_from, string opt_memo ); - - /** - * Transfers a public balance from from_account_id_or_name to one or more blinded balances using a - * stealth transfer. - * - * @param from_account_id_or_name account id or name - * @param asset_symbol asset symbol - * @param to_amounts map from key or label to amount - * @param broadcast true to broadcast the transaction on the network - * @returns blind confirmation structure - */ - blind_confirmation transfer_to_blind( string from_account_id_or_name, - string asset_symbol, - vector> to_amounts, - bool broadcast = false ); - - /** - * Transfers funds from a set of blinded balances to a public account balance. - */ - blind_confirmation transfer_from_blind( - string from_blind_account_key_or_label, - string to_account_id_or_name, - string amount, - string asset_symbol, - bool broadcast = false ); - - /** - * Used to transfer from one set of blinded balances to another - */ - blind_confirmation blind_transfer( string from_key_or_label, - string to_key_or_label, - string amount, - string symbol, - bool broadcast = false ); - /** Place a limit order attempting to sell one asset for another. * * Buying and selling are the same operation on Graphene; if you want to buy BTS @@ -1051,7 +1100,7 @@ class wallet_api signed_transaction borrow_asset(string borrower_name, string amount_to_borrow, string asset_symbol, string amount_of_collateral, bool broadcast = false); - /** Cancel an existing order + /** Cancel an existing order. * * @param order_id the id of order to be cancelled * @param broadcast true to broadcast the transaction on the network @@ -1111,6 +1160,7 @@ class wallet_api bool broadcast = false); /** Update the core options on an asset. + * * There are a number of options which all assets in the network use. These options are * enumerated in the asset_object::asset_options struct. This command is used to update * these options for an existing asset. @@ -1317,6 +1367,7 @@ class wallet_api bool broadcast = false); /** Lists all witnesses registered in the blockchain. + * * This returns a list of all account names that own witnesses, and the associated witness id, * sorted by name. This lists witnesses whether they are currently voted in or not. * @@ -1347,6 +1398,7 @@ class wallet_api map list_committee_members(const string& lowerbound, uint32_t limit); /** Lists all workers in the blockchain. + * * This returns a list of all account names that own worker, and the associated worker id, * sorted by name. This lists workers whether they are currently voted in or not. * @@ -1488,6 +1540,7 @@ class wallet_api bool broadcast = false); /** Lists all SONs in the blockchain. + * * This returns a list of all account names that own SON, and the associated SON id, * sorted by name. This lists SONs whether they are currently voted in or not. * @@ -1502,18 +1555,31 @@ class wallet_api */ map list_sons(const string& lowerbound, uint32_t limit); - /** Lists active at the moment SONs. - * This returns a list of all account names that own active SON, and the associated SON id, - * sorted by name. - * @returns a list of active SONs mapping SON names to SON ids + /** + * @brief Get list of active sons + * @return List of active SONs */ - map list_active_sons(); + flat_map> get_active_sons(); + + /** + * @brief Get list of active sons + * @param sidechain Sidechain type [bitcoin|ethereum|hive] + * @return List of active SONs + */ + vector get_active_sons_by_sidechain(sidechain_type sidechain); /** * @brief Get SON network status - * @return SON network status description + * @return SON network status description for a given sidechain type */ - map get_son_network_status(); + map> get_son_network_status(); + + /** + * @brief Get SON network status + * @param sidechain Sidechain type [bitcoin|ethereum|hive] + * @return SON network status description for a given sidechain type + */ + map get_son_network_status_by_sidechain(sidechain_type sidechain); /** * @brief Get active SON wallet @@ -1616,6 +1682,7 @@ class wallet_api * @param url Same as for create_witness. The empty string makes it remain the same. * @param block_signing_key The new block signing public key. The empty string makes it remain the same. * @param broadcast true if you wish to broadcast the transaction. + * @return the signed transaction */ signed_transaction update_witness(string witness_name, string url, @@ -1634,6 +1701,7 @@ class wallet_api * @param url Any text * @param worker_settings {"type" : "burn"|"refund"|"vesting", "pay_vesting_period_days" : x} * @param broadcast true if you wish to broadcast the transaction. + * @return the signed transaction */ signed_transaction create_worker( string owner_account, @@ -1652,6 +1720,7 @@ class wallet_api * @param account The account which will pay the fee and update votes. * @param delta {"vote_for" : [...], "vote_against" : [...], "vote_abstain" : [...]} * @param broadcast true if you wish to broadcast the transaction. + * @return the signed transaction */ signed_transaction update_worker_votes( string account, @@ -1666,7 +1735,7 @@ class wallet_api * @param asset_symbol the symbol of the asset to vest * @param vesting_type "normal", "gpos" or "son" * @param broadcast true to broadcast the transaction on the network - * @returns the signed transaction registering a vesting object + * @return the signed transaction registering a vesting object */ signed_transaction create_vesting_balance(string owner_account, string amount, @@ -1678,6 +1747,7 @@ class wallet_api * Get information about a vesting balance object. * * @param account_name An account name, account ID, or vesting balance object ID. + * @return a list of vesting balance objects with additional info */ vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ); @@ -1688,6 +1758,7 @@ class wallet_api * @param amount The amount to withdraw. * @param asset_symbol The symbol of the asset to withdraw. * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction */ signed_transaction withdraw_vesting( string witness_name, @@ -1695,13 +1766,13 @@ class wallet_api string asset_symbol, bool broadcast = false); - /** - * Withdraw a GPOS vesting balance. + /** Withdraw a GPOS vesting balance. * * @param account_name The account name of the witness/user, also accepts account ID or vesting balance ID type. * @param amount The amount to withdraw. * @param asset_symbol The symbol of the asset to withdraw. * @param broadcast true if you wish to broadcast the transaction + * @return the signed transaction */ signed_transaction withdraw_GPOS_vesting_balance( string account_name, @@ -1743,6 +1814,7 @@ class wallet_api * * @param voting_account the name or id of the account who is voting with their shares * @param son the name or id of the SONs' owner account + * @param sidechain the name of the sidechain * @param approve true if you wish to vote in favor of that SON, false to * remove your vote in favor of that SON * @param broadcast true if you wish to broadcast the transaction @@ -1750,6 +1822,7 @@ class wallet_api */ signed_transaction vote_for_son(string voting_account, string son, + sidechain_type sidechain, bool approve, bool broadcast = false); @@ -1773,6 +1846,8 @@ class wallet_api * @param sons_to_reject the names or ids of the SONs owner accounts you wish * to reject (these will be removed from the list of SONs * you currently approve). This list can be empty. + * @param sidechain the name of the sidechain + * * @param desired_number_of_sons the number of SONs you believe the network * should have. You must vote for at least this many * SONs. You can set this to 0 to abstain from @@ -1783,6 +1858,7 @@ class wallet_api signed_transaction update_son_votes(string voting_account, std::vector sons_to_approve, std::vector sons_to_reject, + sidechain_type sidechain, uint16_t desired_number_of_sons, bool broadcast = false); @@ -2056,6 +2132,13 @@ class wallet_api bool broadcast /* = false */ ); + /** + * Returns the order book for the market base:quote. + * @param base symbol or ID of the base asset + * @param quote symbol or ID of the quote asset + * @param limit depth of the order book to retrieve, for bids and asks each, capped at 50 + * @return Order book of the market + */ order_book get_order_book( const string& base, const string& quote, unsigned limit = 50); asset get_total_matched_bet_amount_for_betting_market_group(betting_market_group_id_type group_id); @@ -2482,9 +2565,29 @@ class wallet_api /** * @brief Returns all tokens + * @param limit the maximum number of NFT objects to return (max: 100) + * @param lower_id ID of the first NFT object to include in the list. * @return Returns vector of NFT objects, empty vector if none */ - vector nft_get_all_tokens() const; + vector nft_get_all_tokens(uint32_t limit, optional lower_id) const; + + /** + * @brief Returns all tokens owned by owner + * @param owner NFT owner account ID + * @param limit the maximum number of NFT objects to return (max: 100) + * @param lower_id ID of the first NFT object to include in the list. + * @return Returns vector of NFT objects, empty vector if none + */ + vector nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const; + + /** + * @brief Returns all NFT metadata objects owned by owner + * @param owner NFT owner account ID + * @param limit the maximum number of NFT metadata objects to return (max: 100) + * @param lower_id ID of the first NFT metadata object to include in the list. + * @return Returns vector of NFT metadata objects, empty vector if none + */ + vector nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const; signed_transaction nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast ); signed_transaction create_offer(set item_ids, @@ -2547,16 +2650,6 @@ class wallet_api void network_add_nodes( const vector& nodes ); vector< variant > network_get_connected_peers(); - /** - * Used to transfer from one set of blinded balances to another - */ - blind_confirmation blind_transfer_help( string from_key_or_label, - string to_key_or_label, - string amount, - string symbol, - bool broadcast = false, - bool to_temp = false ); - std::map> get_result_formatters() const; /** @@ -2597,6 +2690,19 @@ class wallet_api */ std::map> get_son_listener_log() const; + /** + * @brief Estimate transaction fee for withdrawal + * @param sidechain Sidechain type (bitcoin, HIVE, etc) + * @return Transaction fee + */ + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain) const; + + /** + * @brief Estimate gas fee for withdrawal transaction for ETH + * @return Gas fee in ETH + */ + std::string eth_estimate_withdrawal_transaction_fee() const; + fc::signal lock_changed; std::shared_ptr my; void encrypt_keys(); @@ -2607,9 +2713,6 @@ class wallet_api extern template class fc::api; FC_REFLECT( graphene::wallet::key_label, (label)(key) ) -FC_REFLECT( graphene::wallet::blind_balance, (amount)(from)(to)(one_time_key)(blinding_factor)(commitment)(used) ) -FC_REFLECT( graphene::wallet::blind_confirmation::output, (label)(pub_key)(decrypted_memo)(confirmation)(auth)(confirmation_receipt) ) -FC_REFLECT( graphene::wallet::blind_confirmation, (trx)(outputs) ) FC_REFLECT( graphene::wallet::plain_keys, (keys)(checksum) ) @@ -2620,7 +2723,6 @@ FC_REFLECT( graphene::wallet::wallet_data, (extra_keys) (pending_account_registrations)(pending_witness_registrations) (labeled_keys) - (blind_receipts) (committed_game_moves) (ws_server) (ws_user) @@ -2639,9 +2741,6 @@ FC_REFLECT( graphene::wallet::exported_account_keys, (account_name)(encrypted_pr FC_REFLECT( graphene::wallet::exported_keys, (password_checksum)(account_keys) ) -FC_REFLECT( graphene::wallet::blind_receipt, - (date)(from_key)(from_label)(to_key)(to_label)(amount)(memo)(control_authority)(data)(used)(conf) ) - FC_REFLECT( graphene::wallet::approval_delta, (active_approvals_to_add) (active_approvals_to_remove) @@ -2657,9 +2756,6 @@ FC_REFLECT( graphene::wallet::worker_vote_delta, (vote_abstain) ) -FC_REFLECT_DERIVED( graphene::wallet::signed_block_with_info, (graphene::chain::signed_block), - (block_id)(signing_key)(transaction_ids) ) - FC_REFLECT_DERIVED( graphene::wallet::vesting_balance_object_with_info, (graphene::chain::vesting_balance_object), (allowed_withdraw)(allowed_withdraw_time) ) @@ -2743,8 +2839,10 @@ FC_API( graphene::wallet::wallet_api, (update_son_vesting_balances) (activate_deregistered_son) (list_sons) - (list_active_sons) + (get_active_sons) + (get_active_sons_by_sidechain) (get_son_network_status) + (get_son_network_status_by_sidechain) (request_son_maintenance) (cancel_request_son_maintenance) (get_active_son_wallet) @@ -2775,6 +2873,7 @@ FC_API( graphene::wallet::wallet_api, (get_account) (get_account_id) (get_block) + (get_block2) (get_blocks) (get_account_count) (get_account_history) @@ -2814,15 +2913,6 @@ FC_API( graphene::wallet::wallet_api, (set_key_label) (get_key_label) (get_public_key) - (get_blind_accounts) - (get_my_blind_accounts) - (get_blind_balances) - (create_blind_account) - (transfer_to_blind) - (transfer_from_blind) - (blind_transfer) - (blind_history) - (receive_blind_transfer) (list_sports) (list_event_groups) (list_betting_market_groups) @@ -2865,6 +2955,8 @@ FC_API( graphene::wallet::wallet_api, (nft_get_approved) (nft_is_approved_for_all) (nft_get_all_tokens) + (nft_get_tokens_by_owner) + (nft_get_metadata_by_owner) (nft_lottery_buy_ticket) (create_offer) (create_bid) @@ -2911,4 +3003,6 @@ FC_API( graphene::wallet::wallet_api, (get_voters_by_id) (get_voters) (get_son_listener_log) + (estimate_withdrawal_transaction_fee) + (eth_estimate_withdrawal_transaction_fee) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index d2fac215..f01e2605 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -134,8 +134,6 @@ public: std::string operator()(const T& op)const; std::string operator()(const transfer_operation& op)const; - std::string operator()(const transfer_from_blind_operation& op)const; - std::string operator()(const transfer_to_blind_operation& op)const; std::string operator()(const account_create_operation& op)const; std::string operator()(const account_update_operation& op)const; std::string operator()(const asset_create_operation& op)const; @@ -2158,8 +2156,8 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (owner_account)(url)(block_signing_key)(broadcast) ) } - signed_transaction activate_deregistered_son(const string & owner_account, - bool broadcast /* = false */) + signed_transaction activate_deregistered_son(const string & owner_account, + bool broadcast /* = false */) { try { son_object son = get_son(owner_account); @@ -2232,68 +2230,24 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (owner_account) ) } - map list_active_sons() + flat_map> get_active_sons() { try { - global_property_object gpo = get_global_properties(); - vector son_ids; - son_ids.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), - std::inserter(son_ids, son_ids.end()), - [](const son_info& swi) { - return swi.son_id; - }); - std::vector> son_objects = _remote_db->get_sons(son_ids); - vector owners; - for(auto obj: son_objects) - { - std::string acc_id = account_id_to_string(obj->son_account); - owners.push_back(acc_id); - } - vector< optional< account_object> > accs = _remote_db->get_accounts(owners); - std::remove_if(son_objects.begin(), son_objects.end(), - [](const fc::optional& obj) -> bool { return obj.valid(); }); - map result; - std::transform(accs.begin(), accs.end(), son_objects.begin(), - std::inserter(result, result.end()), - [](fc::optional& acct, fc::optional son) { - FC_ASSERT(acct, "Invalid active SONs list in global properties."); - return std::make_pair(string(acct->name), std::move(son->id)); - }); - return result; + return _remote_db->get_active_sons(); } FC_CAPTURE_AND_RETHROW() } - map get_son_network_status() + vector get_active_sons_by_sidechain(sidechain_type sidechain) { try { - global_property_object gpo = get_global_properties(); - vector son_ids; - son_ids.reserve(gpo.active_sons.size()); - std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), - std::inserter(son_ids, son_ids.end()), - [](const son_info& swi) { - return swi.son_id; - }); + return _remote_db->get_active_sons_by_sidechain(sidechain); + } FC_CAPTURE_AND_RETHROW() } - map result; - std::vector> son_objects = _remote_db->get_sons(son_ids); - for(auto son_obj: son_objects) { - string status; - if (son_obj) { - son_statistics_object sso = get_object(son_obj->statistics); - if (sso.last_active_timestamp + fc::seconds(gpo.parameters.son_heartbeat_frequency()) > time_point::now()) { - status = "OK, regular SON heartbeat"; - } else { - if (sso.last_active_timestamp + fc::seconds(gpo.parameters.son_down_time()) > time_point::now()) { - status = "OK, irregular SON heartbeat, but not triggering SON down proposal"; - } else { - status = "NOT OK, irregular SON heartbeat, triggering SON down proposal"; - } - } - } else { - status = "NOT OK, invalid SON id"; - } - result[son_obj->id] = status; - } - return result; + map> get_son_network_status() + { try { + return _remote_db->get_son_network_status(); + } FC_CAPTURE_AND_RETHROW() } + + map get_son_network_status_by_sidechain(sidechain_type sidechain) + { try { + return _remote_db->get_son_network_status_by_sidechain(sidechain); } FC_CAPTURE_AND_RETHROW() } optional get_active_son_wallet() @@ -2408,7 +2362,7 @@ public: op.sidechain = sidechain; op.peerplays_uid = peerplays_uid; op.peerplays_transaction_id = peerplays_transaction_id; - op.peerplays_from = peerplays_from; + op.peerplays_from = peerplays_from; op.peerplays_asset = asset(asset_val.amount * asset_price.base.amount / asset_price.quote.amount); op.withdraw_sidechain = withdraw_sidechain; op.withdraw_address = withdraw_address; @@ -2802,6 +2756,7 @@ public: signed_transaction vote_for_son(string voting_account, string son, + sidechain_type sidechain, bool approve, bool broadcast /* = false */) { try { @@ -2815,23 +2770,37 @@ public: account_object voting_account_object = get_account(voting_account); account_id_type son_account_id = get_account_id(son); fc::optional son_obj = _remote_db->get_son_by_account_id(son_account_id); - if (!son_obj) - FC_THROW("Account ${son} is not registered as a son", ("son", son)); + FC_ASSERT(son_obj, "Account ${son} is not registered as a son", ("son", son)); + FC_ASSERT(sidechain == sidechain_type::bitcoin || sidechain == sidechain_type::hive || sidechain == sidechain_type::ethereum, "Unexpected sidechain type"); + + bool update_vote_time = false; if (approve) { - auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); - if (!insert_result.second) - FC_THROW("Account ${account} was already voting for son ${son}", ("account", voting_account)("son", son)); + FC_ASSERT(son_obj->get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", *son_obj)); + account_id_type stake_account = get_account_id(voting_account); + const auto gpos_info = _remote_db->get_gpos_info(stake_account); + const auto vesting_subperiod = _remote_db->get_global_properties().parameters.gpos_subperiod(); + const auto gpos_start_time = fc::time_point_sec(_remote_db->get_global_properties().parameters.gpos_period_start()); + const auto subperiod_start_time = gpos_start_time.sec_since_epoch() + (gpos_info.current_subperiod - 1) * vesting_subperiod; + + auto insert_result = voting_account_object.options.votes.insert(*son_obj->get_sidechain_vote_id(sidechain)); + if (!insert_result.second && (gpos_info.last_voted_time.sec_since_epoch() >= subperiod_start_time)) + FC_THROW("Account ${account} was already voting for son ${son} in the current GPOS sub-period", ("account", voting_account)("son", son)); + else + update_vote_time = true; //Allow user to vote in each sub-period(Update voting time, which is reference in calculating VF) } else { - unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + FC_ASSERT(son_obj->get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", *son_obj)); + unsigned votes_removed = voting_account_object.options.votes.erase(*son_obj->get_sidechain_vote_id(sidechain)); if (!votes_removed) - FC_THROW("Account ${account} is already not voting for son ${son}", ("account", voting_account)("son", son)); + FC_THROW("Account ${account} has already unvoted for son ${son} for sidechain ${sidechain}", ("account", voting_account)("son", son)("sidechain", sidechain)); } + account_update_operation account_update_op; account_update_op.account = voting_account_object.id; account_update_op.new_options = voting_account_object.options; + account_update_op.extensions.value.update_last_voting_time = update_vote_time; signed_transaction tx; tx.operations.push_back( account_update_op ); @@ -2844,6 +2813,7 @@ public: signed_transaction update_son_votes(string voting_account, std::vector sons_to_approve, std::vector sons_to_reject, + sidechain_type sidechain, uint16_t desired_number_of_sons, bool broadcast /* = false */) { try { @@ -2859,9 +2829,11 @@ public: { account_id_type son_owner_account_id = get_account_id(son); fc::optional son_obj = _remote_db->get_son_by_account_id(son_owner_account_id); - if (!son_obj) - FC_THROW("Account ${son} is not registered as a SON", ("son", son)); - auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); + FC_ASSERT(son_obj, "Account ${son} is not registered as a son", ("son", son)); + FC_ASSERT(sidechain == sidechain_type::bitcoin || sidechain == sidechain_type::hive || sidechain == sidechain_type::ethereum, "Unexpected sidechain type"); + + FC_ASSERT(son_obj->get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", *son_obj)); + auto insert_result = voting_account_object.options.votes.insert(*son_obj->get_sidechain_vote_id(sidechain)); if (!insert_result.second) FC_THROW("Account ${account} was already voting for SON ${son}", ("account", voting_account)("son", son)); } @@ -2869,13 +2841,16 @@ public: { account_id_type son_owner_account_id = get_account_id(son); fc::optional son_obj = _remote_db->get_son_by_account_id(son_owner_account_id); - if (!son_obj) - FC_THROW("Account ${son} is not registered as a SON", ("son", son)); - unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); + FC_ASSERT(son_obj, "Account ${son} is not registered as a son", ("son", son)); + FC_ASSERT(sidechain == sidechain_type::bitcoin || sidechain == sidechain_type::hive || sidechain == sidechain_type::ethereum, "Unexpected sidechain type"); + + FC_ASSERT(son_obj->get_sidechain_vote_id(sidechain).valid(), "Invalid vote id, sidechain: ${sidechain}, son: ${son}", ("sidechain", sidechain)("son", *son_obj)); + unsigned votes_removed = voting_account_object.options.votes.erase(*son_obj->get_sidechain_vote_id(sidechain)); if (!votes_removed) FC_THROW("Account ${account} is already not voting for SON ${son}", ("account", voting_account)("son", son)); } - voting_account_object.options.num_son = desired_number_of_sons; + FC_ASSERT( voting_account_object.options.extensions.value.num_son.valid() , "Invalid son number" ); + (*voting_account_object.options.extensions.value.num_son)[sidechain] = desired_number_of_sons; account_update_operation account_update_op; account_update_op.account = voting_account_object.id; @@ -2909,6 +2884,7 @@ public: case sidechain_type::peerplays : return "peerplays"; case sidechain_type::bitcoin : return "bitcoin"; case sidechain_type::hive : return "hive"; + case sidechain_type::ethereum : return "ethereum"; default: FC_THROW("Wrong sidechain type: ${sidechain}", ("sidechain", sidechain)); } @@ -3330,6 +3306,25 @@ public: return sign_transaction(trx, broadcast); } FC_CAPTURE_AND_RETHROW((order_id)) } + sidechain_type get_sidechain_type_from_asset(asset_id_type asset_id) const + { + const auto& gpo = _remote_db->get_global_properties(); + + if(asset_id == gpo.parameters.btc_asset()) + return sidechain_type::bitcoin; + + if(asset_id == gpo.parameters.eth_asset()) + return sidechain_type::ethereum; + + if(asset_id == gpo.parameters.hbd_asset()) + return sidechain_type::hive; + + if(asset_id == gpo.parameters.hive_asset()) + return sidechain_type::hive; + + return sidechain_type::unknown; + } + signed_transaction transfer(string from, string to, string amount, string asset_symbol, string memo, bool broadcast = false) { try { @@ -3362,6 +3357,19 @@ public: set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); tx.validate(); + //! For sidechain withdrawal check if amount is greater than fee + if(to_id == _remote_db->get_global_properties().parameters.son_account()) { + const auto sidechain = get_sidechain_type_from_asset(asset_obj->id); + const auto transaction_fee = estimate_withdrawal_transaction_fee(sidechain); + + if(transaction_fee) { + if (*transaction_fee >= xfer_op.amount) { + FC_THROW("Transaction fee: ${sidechain_fee}, is greater than or equal to the transferred amount ${amount}", + ("sidechain_fee", get_asset(transaction_fee->asset_id).amount_to_pretty_string(transaction_fee->amount))("amount", get_asset(xfer_op.amount.asset_id).amount_to_pretty_string(xfer_op.amount.amount))); + } + } + } + return sign_transaction(tx, broadcast); } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) } @@ -3472,70 +3480,6 @@ public: return ss.str(); }; - - m["get_blind_balances"] = [this](variant result, const fc::variants& a) - { - auto r = result.as>( GRAPHENE_MAX_NESTED_OBJECTS ); - vector asset_recs; - std::transform(r.begin(), r.end(), std::back_inserter(asset_recs), [this](const asset& a) { - return get_asset(a.asset_id); - }); - - std::stringstream ss; - for( unsigned i = 0; i < asset_recs.size(); ++i ) - ss << asset_recs[i].amount_to_pretty_string(r[i]) << "\n"; - - return ss.str(); - }; - m["transfer_to_blind"] = [this](variant result, const fc::variants& a) - { - auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); - std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); - ss << "\n"; - for( const auto& out : r.outputs ) - { - asset_object a = get_asset( out.decrypted_memo.amount.asset_id ); - ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n"; - } - return ss.str(); - }; - m["blind_transfer"] = [this](variant result, const fc::variants& a) - { - auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); - std::stringstream ss; - r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) ); - ss << "\n"; - for( const auto& out : r.outputs ) - { - asset_object a = get_asset( out.decrypted_memo.amount.asset_id ); - ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n"; - } - return ss.str(); - }; - m["receive_blind_transfer"] = [this](variant result, const fc::variants& a) - { - auto r = result.as( GRAPHENE_MAX_NESTED_OBJECTS ); - std::stringstream ss; - asset_object as = get_asset( r.amount.asset_id ); - ss << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n"; - return ss.str(); - }; - m["blind_history"] = [this](variant result, const fc::variants& a) - { - auto records = result.as>( GRAPHENE_MAX_NESTED_OBJECTS ); - std::stringstream ss; - ss << "WHEN " - << " " << "AMOUNT" << " " << "FROM" << " => " << "TO" << " " << "MEMO" <<"\n"; - ss << "====================================================================================\n"; - for( auto& r : records ) - { - asset_object as = get_asset( r.amount.asset_id ); - ss << fc::get_approximate_relative_time_string( r.date ) - << " " << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n"; - } - return ss.str(); - }; m["get_upcoming_tournaments"] = m["get_tournaments"] = m["get_tournaments_by_state"] = [this](variant result, const fc::variants& a) { const vector tournaments = result.as >( GRAPHENE_MAX_NESTED_OBJECTS ); @@ -4173,7 +4117,7 @@ public: "to access the debug API on the node you are connecting to.\n" "\n" "To fix this problem:\n" - "- Please ensure you are running debug_node, not witness_node.\n" + "- Please ensure you enable debug_witness plugin in witness_node.\n" "- Please follow the instructions in README.md to set up an apiaccess file.\n" "\n"; } @@ -4325,6 +4269,31 @@ public: FC_CAPTURE_AND_RETHROW() } + optional estimate_withdrawal_transaction_fee(sidechain_type sidechain) + { + use_sidechain_api(); + try + { + return (*_remote_sidechain)->estimate_withdrawal_transaction_fee(sidechain); + } + FC_CAPTURE_AND_RETHROW() + } + + std::string eth_estimate_withdrawal_transaction_fee() + { + try + { + const auto transaction_fee = estimate_withdrawal_transaction_fee(sidechain_type::ethereum); + if(transaction_fee) + { + return get_asset(transaction_fee->asset_id).amount_to_pretty_string(transaction_fee->amount); + } + + return "Can't get fee value"; + } + FC_CAPTURE_AND_RETHROW() + } + string _wallet_filename; wallet_data _wallet; @@ -4396,26 +4365,6 @@ std::string operation_printer::operator()(const T& op)const out << " result: " << str_result; return ""; } -std::string operation_printer::operator()(const transfer_from_blind_operation& op)const -{ - auto a = wallet.get_asset( op.fee.asset_id ); - auto receiver = wallet.get_account( op.to ); - - out << receiver.name - << " received " << a.amount_to_pretty_string( op.amount ) << " from blinded balance"; - return ""; -} -std::string operation_printer::operator()(const transfer_to_blind_operation& op)const -{ - auto fa = wallet.get_asset( op.fee.asset_id ); - auto a = wallet.get_asset( op.amount.asset_id ); - auto sender = wallet.get_account( op.from ); - - out << sender.name - << " sent " << a.amount_to_pretty_string( op.amount ) << " to " << op.outputs.size() << " blinded balance" << (op.outputs.size()>1?"s":"") - << " fee: " << fa.amount_to_pretty_string( op.fee ); - return ""; -} string operation_printer::operator()(const transfer_operation& op) const { out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount) @@ -4615,11 +4564,16 @@ bool wallet_api::copy_wallet_file(string destination_filename) return my->copy_wallet_file(destination_filename); } -optional wallet_api::get_block(uint32_t num) +optional wallet_api::get_block(uint32_t num) { return my->_remote_db->get_block(num); } +optional wallet_api::get_block2(uint32_t num) +{ + return my->_remote_db->get_block2(num); +} + vector> wallet_api::get_blocks(uint32_t block_num_from, uint32_t block_num_to) const { return my->_remote_db->get_blocks(block_num_from, block_num_to); @@ -5355,16 +5309,26 @@ map wallet_api::list_sons(const string& lowerbound, uint32_ return my->_remote_db->lookup_son_accounts(lowerbound, limit); } -map wallet_api::list_active_sons() +flat_map> wallet_api::get_active_sons() { - return my->list_active_sons(); + return my->get_active_sons(); } -map wallet_api::get_son_network_status() +vector wallet_api::get_active_sons_by_sidechain(sidechain_type sidechain) +{ + return my->get_active_sons_by_sidechain(sidechain); +} + +map> wallet_api::get_son_network_status() { return my->get_son_network_status(); } +map wallet_api::get_son_network_status_by_sidechain(sidechain_type sidechain) +{ + return my->get_son_network_status_by_sidechain(sidechain); +} + optional wallet_api::get_active_son_wallet() { return my->get_active_son_wallet(); @@ -5410,7 +5374,7 @@ signed_transaction wallet_api::sidechain_withdrawal_transaction(const string &so const string &withdraw_amount) { return my->sidechain_withdrawal_transaction(son_name_or_id, - block_num, + block_num, sidechain, peerplays_uid, peerplays_transaction_id, @@ -5514,19 +5478,21 @@ signed_transaction wallet_api::vote_for_committee_member(string voting_account, signed_transaction wallet_api::vote_for_son(string voting_account, string son, + sidechain_type sidechain, bool approve, bool broadcast /* = false */) { - return my->vote_for_son(voting_account, son, approve, broadcast); + return my->vote_for_son(voting_account, son, sidechain, approve, broadcast); } signed_transaction wallet_api::update_son_votes(string voting_account, std::vector sons_to_approve, std::vector sons_to_reject, + sidechain_type sidechain, uint16_t desired_number_of_sons, bool broadcast /* = false */) { - return my->update_son_votes(voting_account, sons_to_approve, sons_to_reject, desired_number_of_sons, broadcast); + return my->update_son_votes(voting_account, sons_to_approve, sons_to_reject, sidechain, desired_number_of_sons, broadcast); } signed_transaction wallet_api::sidechain_deposit_transaction( const string &son_name_or_id, @@ -5540,7 +5506,7 @@ signed_transaction wallet_api::sidechain_deposit_transaction( const string &son const string &peerplays_from_name_or_id, const string &peerplays_to_name_or_id) { - return my->sidechain_deposit_transaction(son_name_or_id, + return my->sidechain_deposit_transaction(son_name_or_id, sidechain, transaction_id, operation_index, @@ -5817,54 +5783,12 @@ string wallet_api::gethelp(const string& method)const std::stringstream ss; ss << "\n"; - if( method == "import_key" ) - { - ss << "usage: import_key ACCOUNT_NAME_OR_ID WIF_PRIVATE_KEY\n\n"; - ss << "example: import_key \"1.3.11\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\n"; - ss << "example: import_key \"usera\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\n"; - } - else if( method == "transfer" ) - { - ss << "usage: transfer FROM TO AMOUNT SYMBOL \"memo\" BROADCAST\n\n"; - ss << "example: transfer \"1.3.11\" \"1.3.4\" 1000.03 CORE \"memo\" true\n"; - ss << "example: transfer \"usera\" \"userb\" 1000.123 CORE \"memo\" true\n"; - } - else if( method == "create_account_with_brain_key" ) - { - ss << "usage: create_account_with_brain_key BRAIN_KEY ACCOUNT_NAME REGISTRAR REFERRER BROADCAST\n\n"; - ss << "example: create_account_with_brain_key \"my really long brain key\" \"newaccount\" \"1.3.11\" \"1.3.11\" true\n"; - ss << "example: create_account_with_brain_key \"my really long brain key\" \"newaccount\" \"someaccount\" \"otheraccount\" true\n"; - ss << "\n"; - ss << "This method should be used if you would like the wallet to generate new keys derived from the brain key.\n"; - ss << "The BRAIN_KEY will be used as the owner key, and the active key will be derived from the BRAIN_KEY. Use\n"; - ss << "register_account if you already know the keys you know the public keys that you would like to register.\n"; - - } - else if( method == "register_account" ) - { - ss << "usage: register_account ACCOUNT_NAME OWNER_PUBLIC_KEY ACTIVE_PUBLIC_KEY REGISTRAR REFERRER REFERRER_PERCENT BROADCAST\n\n"; - ss << "example: register_account \"newaccount\" \"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\" \"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\" \"1.3.11\" \"1.3.11\" 50 true\n"; - ss << "\n"; - ss << "Use this method to register an account for which you do not know the private keys."; - } - else if( method == "create_asset" ) - { - ss << "usage: ISSUER SYMBOL PRECISION_DIGITS OPTIONS BITASSET_OPTIONS BROADCAST\n\n"; - ss << "PRECISION_DIGITS: the number of digits after the decimal point\n\n"; - ss << "Example value of OPTIONS: \n"; - ss << fc::json::to_pretty_string( graphene::chain::asset_options() ); - ss << "\nExample value of BITASSET_OPTIONS: \n"; - ss << fc::json::to_pretty_string( graphene::chain::bitasset_options() ); - ss << "\nBITASSET_OPTIONS may be null\n"; - } + std::string doxygenHelpString = my->method_documentation.get_detailed_description(method); + if (!doxygenHelpString.empty()) + ss << doxygenHelpString; else - { - std::string doxygenHelpString = my->method_documentation.get_detailed_description(method); - if (!doxygenHelpString.empty()) - ss << doxygenHelpString; - else - ss << "No help defined for method " << method << "\n"; - } + ss << "No help defined for method " << method << "\n"; + return ss.str(); } @@ -6186,495 +6110,6 @@ bool wallet_api::set_key_label( public_key_type key, string label } return false; } -map wallet_api::get_blind_accounts()const -{ - map result; - for( const auto& item : my->_wallet.labeled_keys ) - result[item.label] = item.key; - return result; -} -map wallet_api::get_my_blind_accounts()const -{ - FC_ASSERT( !is_locked(), "Wallet is locked, please unlock to get all operations available" ); - map result; - for( const auto& item : my->_wallet.labeled_keys ) - { - if( my->_keys.find(item.key) != my->_keys.end() ) - result[item.label] = item.key; - } - return result; -} - -public_key_type wallet_api::create_blind_account( string label, string brain_key ) -{ - FC_ASSERT( !is_locked(), "Wallet is locked, please unlock to get all operations available" ); - - auto label_itr = my->_wallet.labeled_keys.get().find(label); - if( label_itr != my->_wallet.labeled_keys.get().end() ) - FC_ASSERT( !"Key with label already exists" ); - brain_key = fc::trim_and_normalize_spaces( brain_key ); - auto secret = fc::sha256::hash( brain_key.c_str(), brain_key.size() ); - auto priv_key = fc::ecc::private_key::regenerate( secret ); - public_key_type pub_key = priv_key.get_public_key(); - - FC_ASSERT( set_key_label( pub_key, label ) ); - - my->_keys[pub_key] = graphene::utilities::key_to_wif( priv_key ); - - save_wallet_file(); - return pub_key; -} - -vector wallet_api::get_blind_balances( string key_or_label ) -{ - vector result; - map balances; - - vector used; - - auto pub_key = get_public_key( key_or_label ); - auto& to_asset_used_idx = my->_wallet.blind_receipts.get(); - auto start = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,asset_id_type(0),false) ); - auto end = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,asset_id_type(uint32_t(0xffffffff)),true) ); - while( start != end ) - { - if( !start->used ) - { - auto answer = my->_remote_db->get_blinded_balances( {start->commitment()} ); - if( answer.size() ) - balances[start->amount.asset_id] += start->amount.amount; - else - used.push_back( start->commitment() ); - } - ++start; - } - for( const auto& u : used ) - { - auto itr = my->_wallet.blind_receipts.get().find( u ); - my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } ); - } - for( auto item : balances ) - result.push_back( asset( item.second, item.first ) ); - return result; -} - -blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_key_or_label, - string to_account_id_or_name, - string amount_in, - string symbol, - bool broadcast ) -{ try { - transfer_from_blind_operation from_blind; - - - auto fees = my->_remote_db->get_global_properties().parameters.current_fees; - fc::optional asset_obj = get_asset(symbol); - FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol)); - auto amount = asset_obj->amount_from_string(amount_in); - - from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate ); - - auto blind_in = asset_obj->amount_to_string( from_blind.fee + amount ); - - - auto conf = blind_transfer_help( from_blind_account_key_or_label, - from_blind_account_key_or_label, - blind_in, symbol, false, true/*to_temp*/ ); - FC_ASSERT( conf.outputs.size() > 0 ); - - auto to_account = my->get_account( to_account_id_or_name ); - from_blind.to = to_account.id; - from_blind.amount = amount; - from_blind.blinding_factor = conf.outputs.back().decrypted_memo.blinding_factor; - from_blind.inputs.push_back( {conf.outputs.back().decrypted_memo.commitment, authority() } ); - from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate ); - - idump( (from_blind) ); - conf.trx.operations.push_back(from_blind); - ilog( "about to validate" ); - conf.trx.validate(); - - if( broadcast && conf.outputs.size() == 2 ) { - - // Save the change - blind_confirmation::output conf_output; - blind_confirmation::output change_output = conf.outputs[0]; - - // The wallet must have a private key for confirmation.to, this is used to decrypt the memo - public_key_type from_key = get_public_key(from_blind_account_key_or_label); - conf_output.confirmation.to = from_key; - conf_output.confirmation.one_time_key = change_output.confirmation.one_time_key; - conf_output.confirmation.encrypted_memo = change_output.confirmation.encrypted_memo; - conf_output.confirmation_receipt = conf_output.confirmation; - //try { - receive_blind_transfer( conf_output.confirmation_receipt, from_blind_account_key_or_label, "@"+to_account.name ); - //} catch ( ... ){} - } - - ilog( "about to broadcast" ); - conf.trx = sign_transaction( conf.trx, broadcast ); - - return conf; -} FC_CAPTURE_AND_RETHROW( (from_blind_account_key_or_label)(to_account_id_or_name)(amount_in)(symbol) ) } - -blind_confirmation wallet_api::blind_transfer( string from_key_or_label, - string to_key_or_label, - string amount_in, - string symbol, - bool broadcast ) -{ - return blind_transfer_help( from_key_or_label, to_key_or_label, amount_in, symbol, broadcast, false ); -} -blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label, - string to_key_or_label, - string amount_in, - string symbol, - bool broadcast, - bool to_temp ) -{ - blind_confirmation confirm; - try { - - FC_ASSERT( !is_locked(), "Wallet is locked, please unlock to get all operations available" ); - public_key_type from_key = get_public_key(from_key_or_label); - public_key_type to_key = get_public_key(to_key_or_label); - - fc::optional asset_obj = get_asset(symbol); - FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol)); - - blind_transfer_operation blind_tr; - blind_tr.outputs.resize(2); - - auto fees = my->_remote_db->get_global_properties().parameters.current_fees; - - auto amount = asset_obj->amount_from_string(amount_in); - - asset total_amount = asset_obj->amount(0); - - vector blinding_factors; - - //auto from_priv_key = my->get_private_key( from_key ); - - blind_tr.fee = fees->calculate_fee( blind_tr, asset_obj->options.core_exchange_rate ); - - vector used; - - auto& to_asset_used_idx = my->_wallet.blind_receipts.get(); - auto start = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,false) ); - auto end = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,true) ); - while( start != end ) - { - auto result = my->_remote_db->get_blinded_balances( {start->commitment() } ); - if( result.size() == 0 ) - { - used.push_back( start->commitment() ); - } - else - { - blind_tr.inputs.push_back({start->commitment(), start->control_authority}); - blinding_factors.push_back( start->data.blinding_factor ); - total_amount += start->amount; - - if( total_amount >= amount + blind_tr.fee ) - break; - } - ++start; - } - for( const auto& u : used ) - { - auto itr = my->_wallet.blind_receipts.get().find( u ); - my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } ); - } - - FC_ASSERT( total_amount >= amount+blind_tr.fee, "Insufficent Balance", ("available",total_amount)("amount",amount)("fee",blind_tr.fee) ); - - auto one_time_key = fc::ecc::private_key::generate(); - auto secret = one_time_key.get_shared_secret( to_key ); - auto child = fc::sha256::hash( secret ); - auto nonce = fc::sha256::hash( one_time_key.get_secret() ); - auto blind_factor = fc::sha256::hash( child ); - - auto from_secret = one_time_key.get_shared_secret( from_key ); - auto from_child = fc::sha256::hash( from_secret ); - auto from_nonce = fc::sha256::hash( nonce ); - - auto change = total_amount - amount - blind_tr.fee; - fc::sha256 change_blind_factor; - fc::sha256 to_blind_factor; - if( change.amount > 0 ) - { - idump(("to_blind_factor")(blind_factor) ); - blinding_factors.push_back( blind_factor ); - change_blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() - 1 ); - wdump(("change_blind_factor")(change_blind_factor) ); - } - else // change == 0 - { - blind_tr.outputs.resize(1); - blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); - idump(("to_sum_blind_factor")(blind_factor) ); - blinding_factors.push_back( blind_factor ); - idump(("nochange to_blind_factor")(blind_factor) ); - } - fc::ecc::public_key from_pub_key = from_key; - fc::ecc::public_key to_pub_key = to_key; - - blind_output to_out; - to_out.owner = to_temp ? authority() : authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); - to_out.commitment = fc::ecc::blind( blind_factor, amount.amount.value ); - idump(("to_out.blind")(blind_factor)(to_out.commitment) ); - - - if( blind_tr.outputs.size() > 1 ) - { - to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce, 0, 0, amount.amount.value ); - - blind_output change_out; - change_out.owner = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 ); - change_out.commitment = fc::ecc::blind( change_blind_factor, change.amount.value ); - change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce, 0, 0, change.amount.value ); - blind_tr.outputs[1] = change_out; - - - blind_confirmation::output conf_output; - conf_output.label = from_key_or_label; - conf_output.pub_key = from_key; - conf_output.decrypted_memo.from = from_key; - conf_output.decrypted_memo.amount = change; - conf_output.decrypted_memo.blinding_factor = change_blind_factor; - conf_output.decrypted_memo.commitment = change_out.commitment; - conf_output.decrypted_memo.check = from_secret._hash[0]; - conf_output.confirmation.one_time_key = one_time_key.get_public_key(); - conf_output.confirmation.to = from_key; - conf_output.confirmation.encrypted_memo = fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) ); - conf_output.auth = change_out.owner; - conf_output.confirmation_receipt = conf_output.confirmation; - - confirm.outputs.push_back( conf_output ); - } - blind_tr.outputs[0] = to_out; - - blind_confirmation::output conf_output; - conf_output.label = to_key_or_label; - conf_output.pub_key = to_key; - conf_output.decrypted_memo.from = from_key; - conf_output.decrypted_memo.amount = amount; - conf_output.decrypted_memo.blinding_factor = blind_factor; - conf_output.decrypted_memo.commitment = to_out.commitment; - conf_output.decrypted_memo.check = secret._hash[0]; - conf_output.confirmation.one_time_key = one_time_key.get_public_key(); - conf_output.confirmation.to = to_key; - conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) ); - conf_output.auth = to_out.owner; - conf_output.confirmation_receipt = conf_output.confirmation; - - confirm.outputs.push_back( conf_output ); - - /** commitments must be in sorted order */ - std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(), - [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); - std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(), - [&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } ); - - confirm.trx.operations.emplace_back( std::move(blind_tr) ); - ilog( "validate before" ); - confirm.trx.validate(); - confirm.trx = sign_transaction(confirm.trx, broadcast); - - if( broadcast ) - { - for( const auto& out : confirm.outputs ) - { - try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, "" ); } catch ( ... ){} - } - } - - return confirm; -} FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) } - - - -/** - * Transfers a public balance from @from to one or more blinded balances using a - * stealth transfer. - */ -blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name, - string asset_symbol, - /** map from key or label to amount */ - vector> to_amounts, - bool broadcast ) -{ try { - FC_ASSERT( !is_locked(), "Wallet is locked, please unlock to get all operations available" ); - idump((to_amounts)); - - blind_confirmation confirm; - account_object from_account = my->get_account(from_account_id_or_name); - - fc::optional asset_obj = get_asset(asset_symbol); - FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); - - transfer_to_blind_operation bop; - bop.from = from_account.id; - - vector blinding_factors; - - asset total_amount = asset_obj->amount(0); - - for( auto item : to_amounts ) - { - auto one_time_key = fc::ecc::private_key::generate(); - auto to_key = get_public_key( item.first ); - auto secret = one_time_key.get_shared_secret( to_key ); - auto child = fc::sha256::hash( secret ); - auto nonce = fc::sha256::hash( one_time_key.get_secret() ); - auto blind_factor = fc::sha256::hash( child ); - - blinding_factors.push_back( blind_factor ); - - auto amount = asset_obj->amount_from_string(item.second); - total_amount += amount; - - - fc::ecc::public_key to_pub_key = to_key; - blind_output out; - out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 ); - out.commitment = fc::ecc::blind( blind_factor, amount.amount.value ); - if( to_amounts.size() > 1 ) - out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, 0, 0, amount.amount.value ); - - - blind_confirmation::output conf_output; - conf_output.label = item.first; - conf_output.pub_key = to_key; - conf_output.decrypted_memo.amount = amount; - conf_output.decrypted_memo.blinding_factor = blind_factor; - conf_output.decrypted_memo.commitment = out.commitment; - conf_output.decrypted_memo.check = secret._hash[0]; - conf_output.confirmation.one_time_key = one_time_key.get_public_key(); - conf_output.confirmation.to = to_key; - conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) ); - conf_output.confirmation_receipt = conf_output.confirmation; - - confirm.outputs.push_back( conf_output ); - - bop.outputs.push_back(out); - } - bop.amount = total_amount; - bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() ); - - /** commitments must be in sorted order */ - std::sort( bop.outputs.begin(), bop.outputs.end(), - [&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } ); - - confirm.trx.operations.push_back( bop ); - my->set_operation_fees( confirm.trx, my->_remote_db->get_global_properties().parameters.current_fees); - confirm.trx.validate(); - confirm.trx = sign_transaction(confirm.trx, broadcast); - - if( broadcast ) - { - for( const auto& out : confirm.outputs ) - { - try { receive_blind_transfer( out.confirmation_receipt, "@"+from_account.name, "from @"+from_account.name ); } catch ( ... ){} - } - } - - return confirm; -} FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) } - -blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, string opt_from, string opt_memo ) -{ - FC_ASSERT( !is_locked(), "Wallet is locked, please unlock to get all operations available" ); - stealth_confirmation conf(confirmation_receipt); - FC_ASSERT( conf.to ); - - blind_receipt result; - result.conf = conf; - - auto to_priv_key_itr = my->_keys.find( *conf.to ); - FC_ASSERT( to_priv_key_itr != my->_keys.end(), "No private key for receiver", ("conf",conf) ); - - - auto to_priv_key = wif_to_key( to_priv_key_itr->second ); - FC_ASSERT( to_priv_key ); - - auto secret = to_priv_key->get_shared_secret( conf.one_time_key ); - auto child = fc::sha256::hash( secret ); - - auto child_priv_key = to_priv_key->child( child ); - //auto blind_factor = fc::sha256::hash( child ); - - auto plain_memo = fc::aes_decrypt( secret, conf.encrypted_memo ); - auto memo = fc::raw::unpack( plain_memo ); - - result.to_key = *conf.to; - result.to_label = get_key_label( result.to_key ); - if( memo.from ) - { - result.from_key = *memo.from; - result.from_label = get_key_label( result.from_key ); - if( result.from_label == string() ) - { - result.from_label = opt_from; - set_key_label( result.from_key, result.from_label ); - } - } - else - { - result.from_label = opt_from; - } - result.amount = memo.amount; - result.memo = opt_memo; - - // confirm the amount matches the commitment (verify the blinding factor) - auto commtiment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value ); - FC_ASSERT( fc::ecc::verify_sum( {commtiment_test}, {memo.commitment}, 0 ) ); - - blind_balance bal; - bal.amount = memo.amount; - bal.to = *conf.to; - if( memo.from ) bal.from = *memo.from; - bal.one_time_key = conf.one_time_key; - bal.blinding_factor = memo.blinding_factor; - bal.commitment = memo.commitment; - bal.used = false; - - auto child_pubkey = child_priv_key.get_public_key(); - auto owner = authority(1, public_key_type(child_pubkey), 1); - result.control_authority = owner; - result.data = memo; - - auto child_key_itr = owner.key_auths.find( child_pubkey ); - if( child_key_itr != owner.key_auths.end() ) - my->_keys[child_key_itr->first] = key_to_wif( child_priv_key ); - - // my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal ); - - result.date = fc::time_point::now(); - my->_wallet.blind_receipts.insert( result ); - my->_keys[child_pubkey] = key_to_wif( child_priv_key ); - - save_wallet_file(); - - return result; -} - -vector wallet_api::blind_history( string key_or_account ) -{ - vector result; - auto pub_key = get_public_key( key_or_account ); - - if( pub_key == public_key_type() ) - return vector(); - - for( auto& r : my->_wallet.blind_receipts ) - { - if( r.from_key == pub_key || r.to_key == pub_key ) - result.push_back( r ); - } - std::sort( result.begin(), result.end(), [&]( const blind_receipt& a, const blind_receipt& b ){ return a.date > b.date; } ); - return result; -} /////////////// // peerplays // @@ -7612,9 +7047,28 @@ bool wallet_api::nft_is_approved_for_all(string owner_account_id_or_name, string return my->_remote_db->nft_is_approved_for_all(owner_account.id, operator_account.id); } -vector wallet_api::nft_get_all_tokens() const +vector wallet_api::nft_get_all_tokens(uint32_t limit, optional lower_id) const { - return my->_remote_db->nft_get_all_tokens(); + nft_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_all_tokens(lb_id, limit); +} + +vector wallet_api::nft_get_tokens_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const +{ + nft_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_tokens_by_owner(owner, lb_id, limit); +} + +vector wallet_api::nft_get_metadata_by_owner(account_id_type owner, uint32_t limit, optional lower_id) const +{ + nft_metadata_id_type lb_id; + if(lower_id) + lb_id = *lower_id; + return my->_remote_db->nft_get_metadata_by_owner(owner, lb_id, limit); } signed_transaction wallet_api::nft_lottery_buy_ticket( nft_metadata_id_type lottery, account_id_type buyer, uint64_t tickets_to_buy, bool broadcast ) @@ -7855,10 +7309,6 @@ vector wallet_api::get_account_roles_by_owner(string owner_ account_object owner_account = my->get_account(owner_account_id_or_name); return my->_remote_db->get_account_roles_by_owner(owner_account.id); } -// default ctor necessary for FC_REFLECT -signed_block_with_info::signed_block_with_info() -{ -} order_book wallet_api::get_order_book( const string& base, const string& quote, unsigned limit ) { @@ -7915,15 +7365,14 @@ std::map> wallet_api::get_son_listener_ return my->get_son_listener_log(); } -// default ctor necessary for FC_REFLECT -signed_block_with_info::signed_block_with_info( const signed_block& block ) - : signed_block( block ) +optional wallet_api::estimate_withdrawal_transaction_fee(sidechain_type sidechain) const { - block_id = id(); - signing_key = signee(); - transaction_ids.reserve( transactions.size() ); - for( const processed_transaction& tx : transactions ) - transaction_ids.push_back( tx.id() ); + return my->estimate_withdrawal_transaction_fee(sidechain); +} + +std::string wallet_api::eth_estimate_withdrawal_transaction_fee() const +{ + return my->eth_estimate_withdrawal_transaction_fee(); } vesting_balance_object_with_info::vesting_balance_object_with_info() diff --git a/programs/CMakeLists.txt b/programs/CMakeLists.txt index d9c82346..0c3bb135 100644 --- a/programs/CMakeLists.txt +++ b/programs/CMakeLists.txt @@ -3,8 +3,6 @@ if( BUILD_PEERPLAYS_PROGRAMS ) add_subdirectory( cli_wallet ) add_subdirectory( genesis_util ) add_subdirectory( witness_node ) - add_subdirectory( debug_node ) - add_subdirectory( delayed_node ) add_subdirectory( js_operation_serializer ) add_subdirectory( size_checker ) endif( BUILD_PEERPLAYS_PROGRAMS ) diff --git a/programs/debug_node/CMakeLists.txt b/programs/debug_node/CMakeLists.txt deleted file mode 100644 index e39ea929..00000000 --- a/programs/debug_node/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_executable( debug_node main.cpp ) -if( UNIX AND NOT APPLE ) - set(rt_library rt ) -endif() - -find_package( Gperftools QUIET ) -if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling debug_node with TCMalloc") - list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) -endif() - -target_link_libraries( debug_node - PRIVATE graphene_app graphene_egenesis_full ${PLATFORM_SPECIFIC_LIBS} ) - -install( TARGETS - debug_node - - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib -) diff --git a/programs/debug_node/README.md b/programs/debug_node/README.md deleted file mode 100644 index 53d56733..00000000 --- a/programs/debug_node/README.md +++ /dev/null @@ -1,104 +0,0 @@ - -Introduction ------------- - -The `debug_node` is a tool to allow developers to run many interesting sorts of "what-if" tests using state from a production blockchain. -Like "what happens if I produce enough blocks for the next hardfork time to arrive?" or "what would happen if this account (which I don't have a private key for) did this transaction?" - -Setup ------ - -Be sure you've built the right build targets: - - $ make get_dev_key debug_node cli_wallet witness_node - -Use the `get_dev_key` utility to generate a keypair: - - $ programs/genesis_util/get_dev_key "" nathan - [{"private_key":"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3","public_key":"BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","address":"BTSFAbAx7yuxt725qSZvfwWqkdCwp9ZnUama"}] - -Obtain a copy of the blockchain in `block_db` directory: - $ programs/witness_node/witness_node --data-dir data/mydatadir - # ... wait for chain to sync - ^C - $ cp -Rp data/mydatadir/blockchain/database/block_num_to_block ./block_db - -Set up a new datadir with the following `config.ini` settings: - - # setup API endpoint - rpc-endpoint = 127.0.0.1:8090 - # setting this to empty effectively disables the p2p network - seed-nodes = [] - # set apiaccess.json so we can set up - api-access = "data/debug_datadir/api-access.json" - -Then set up `data/debug_datadir/api-access.json` to allow access to the debug API like this: - - { - "permission_map" : - [ - [ - "bytemaster", - { - "password_hash_b64" : "9e9GF7ooXVb9k4BoSfNIPTelXeGOZ5DrgOYMj94elaY=", - "password_salt_b64" : "INDdM6iCi/8=", - "allowed_apis" : ["database_api", "network_broadcast_api", "history_api", "network_node_api", "debug_api"] - } - ], - [ - "*", - { - "password_hash_b64" : "*", - "password_salt_b64" : "*", - "allowed_apis" : ["database_api", "network_broadcast_api", "history_api"] - } - ] - ] - } - -See [here](https://github.com/cryptonomex/graphene#accessing-restricted-apis) for more detail on the `api-access.json` format. - -Once that is set up, run `debug_node` against your newly prepared datadir: - - programs/debug_node/debug_node --data-dir data/debug_datadir - -Run `cli_wallet` to connect to the `debug_node` port, using the username and password to access the new `debug_api` (and also a different wallet file): - - programs/cli_wallet/cli_wallet -s 127.0.0.1:8090 -w debug.wallet -u bytemaster -p supersecret - -Example usage -------------- - -Load some blocks from the datadir: - - dbg_push_blocks block_db 20000 - -Note, when pushing a very large number of blocks sometimes `cli_wallet` hangs and you must Ctrl+C and restart it (leaving the `debug_node` running). - -Generate (fake) blocks with our own private key: - - dbg_generate_blocks 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 1000 - -Update `angel` account to be controlled by our own private key and generate a (fake) transfer: - - dbg_update_object {"_action":"update", "id":"1.2.1090", "active":{"weight_threshold":1,"key_auths":[["BTS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV",1]]}} - import_key angel 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3 - transfer angel init0 999999 BTS "" true - -How it works ------------- - -The commands work by creating diff(s) from the main chain that are applied to the local chain at specified block height(s). It lets you easily check out "what-if" -scenarios in a fantasy debug toy world forked from the real chain, e.g. "if we take all of the blocks until today, then generate a bunch more until a hardfork time -in the future arrives, does the chain stay up? Can I do transactions X, Y, and Z in the wallet after the hardfork?" Anyone connecting to this node sees the same -fantasy world, so you can e.g. make changes with the `cli_wallet` and see them exist in other `cli_wallet` instances (or GUI wallets or API scripts). - -Limitations ------------ - -The main limitations are: - -- No export format for the diffs, so you can't really [1] connect multiple `debug_node` to each other. -- Once faked block(s) or tx(s) have been produced on your chain, you can't really [1] stream blocks or tx's from the main network to your chain. - -[1] It should theoretically be possible, but it's non-trivial and totally untested. diff --git a/programs/debug_node/main.cpp b/programs/debug_node/main.cpp deleted file mode 100644 index c94d3fd2..00000000 --- a/programs/debug_node/main.cpp +++ /dev/null @@ -1,307 +0,0 @@ -/* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. - * - * The MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION 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 -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#ifdef WIN32 -# include -#else -# include -#endif - -using namespace graphene; -namespace bpo = boost::program_options; - -void write_default_logging_config_to_stream(std::ostream& out); -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename); - -int main(int argc, char** argv) { - app::application* node = new app::application(); - fc::oexception unhandled_exception; - try { - bpo::options_description app_options("Graphene Witness Node"); - bpo::options_description cfg_options("Graphene Witness Node"); - app_options.add_options() - ("help,h", "Print this help message and exit.") - ("data-dir,d", bpo::value()->default_value("witness_node_data_dir"), "Directory containing databases, configuration file, etc.") - ; - - bpo::variables_map options; - - auto witness_plug = node->register_plugin(); - auto history_plug = node->register_plugin(); - auto market_history_plug = node->register_plugin(); - - try - { - bpo::options_description cli, cfg; - node->set_program_options(cli, cfg); - app_options.add(cli); - cfg_options.add(cfg); - bpo::store(bpo::parse_command_line(argc, argv, app_options), options); - } - catch (const boost::program_options::error& e) - { - std::cerr << "Error parsing command line: " << e.what() << "\n"; - return 1; - } - - if( options.count("help") ) - { - std::cout << app_options << "\n"; - return 0; - } - - fc::path data_dir; - if( options.count("data-dir") ) - { - data_dir = options["data-dir"].as(); - if( data_dir.is_relative() ) - data_dir = fc::current_path() / data_dir; - } - - fc::path config_ini_path = data_dir / "config.ini"; - if( fc::exists(config_ini_path) ) - { - // get the basic options - bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), cfg_options, true), options); - - // try to get logging options from the config file. - try - { - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - catch (const fc::exception&) - { - wlog("Error parsing logging config from config file ${config}, using default config", ("config", config_ini_path.preferred_string())); - } - } - else - { - ilog("Writing new config file at ${path}", ("path", config_ini_path)); - if( !fc::exists(data_dir) ) - fc::create_directories(data_dir); - - std::ofstream out_cfg(config_ini_path.preferred_string()); - for( const boost::shared_ptr od : cfg_options.options() ) - { - if( !od->description().empty() ) - out_cfg << "# " << od->description() << "\n"; - boost::any store; - if( !od->semantic()->apply_default(store) ) - out_cfg << "# " << od->long_name() << " = \n"; - else { - auto example = od->format_parameter(); - if( example.empty() ) - // This is a boolean switch - out_cfg << od->long_name() << " = " << "false\n"; - else { - // The string is formatted "arg (=)" - example.erase(0, 6); - example.erase(example.length()-1); - out_cfg << od->long_name() << " = " << example << "\n"; - } - } - out_cfg << "\n"; - } - write_default_logging_config_to_stream(out_cfg); - out_cfg.close(); - // read the default logging config we just wrote out to the file and start using it - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - - bpo::notify(options); - node->initialize(data_dir, options); - node->initialize_plugins( options ); - - node->startup(); - node->startup_plugins(); - - fc::promise::ptr exit_promise = new fc::promise("UNIX Signal Handler"); - - fc::set_signal_handler([&exit_promise](int signal) { - elog( "Caught SIGINT attempting to exit cleanly" ); - exit_promise->set_value(signal); - }, SIGINT); - - fc::set_signal_handler([&exit_promise](int signal) { - elog( "Caught SIGTERM attempting to exit cleanly" ); - exit_promise->set_value(signal); - }, SIGTERM); - - ilog("Started witness node on a chain with ${h} blocks.", ("h", node->chain_database()->head_block_num())); - ilog("Chain ID is ${id}", ("id", node->chain_database()->get_chain_id()) ); - - int signal = exit_promise->wait(); - ilog("Exiting from signal ${n}", ("n", signal)); - node->shutdown_plugins(); - node->shutdown(); - delete node; - return 0; - } catch( const fc::exception& e ) { - // deleting the node can yield, so do this outside the exception handler - unhandled_exception = e; - } - - if (unhandled_exception) - { - elog("Exiting with error:\n${e}", ("e", unhandled_exception->to_detail_string())); - node->shutdown(); - delete node; - return 1; - } -} - -// logging config is too complicated to be parsed by boost::program_options, -// so we do it by hand -// -// Currently, you can only specify the filenames and logging levels, which -// are all most users would want to change. At a later time, options can -// be added to control rotation intervals, compression, and other seldom- -// used features -void write_default_logging_config_to_stream(std::ostream& out) -{ - out << "# declare an appender named \"stderr\" that writes messages to the console\n" - "[log.console_appender.stderr]\n" - "stream=std_error\n\n" - "# declare an appender named \"p2p\" that writes messages to p2p.log\n" - "[log.file_appender.p2p]\n" - "filename=logs/p2p/p2p.log\n" - "# filename can be absolute or relative to this config file\n\n" - "# route any messages logged to the default logger to the \"stderr\" logger we\n" - "# declared above, if they are info level are higher\n" - "[logger.default]\n" - "level=info\n" - "appenders=stderr\n\n" - "# route messages sent to the \"p2p\" logger to the p2p appender declared above\n" - "[logger.p2p]\n" - "level=info\n" - "appenders=p2p\n\n"; -} - -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename) -{ - try - { - fc::logging_config logging_config; - bool found_logging_config = false; - - boost::property_tree::ptree config_ini_tree; - boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree); - for (const auto& section : config_ini_tree) - { - const std::string& section_name = section.first; - const boost::property_tree::ptree& section_tree = section.second; - - const std::string console_appender_section_prefix = "log.console_appender."; - const std::string file_appender_section_prefix = "log.file_appender."; - const std::string logger_section_prefix = "logger."; - - if (boost::starts_with(section_name, console_appender_section_prefix)) - { - std::string console_appender_name = section_name.substr(console_appender_section_prefix.length()); - std::string stream_name = section_tree.get("stream"); - - // construct a default console appender config here - // stdout/stderr will be taken from ini file, everything else hard-coded here - fc::console_appender::config console_appender_config; - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::debug, - fc::console_appender::color::green)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::warn, - fc::console_appender::color::brown)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::error, - fc::console_appender::color::cyan)); - console_appender_config.stream = fc::variant(stream_name, 1).as(1); - logging_config.appenders.push_back(fc::appender_config(console_appender_name, "console", fc::variant(console_appender_config, 20))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, file_appender_section_prefix)) - { - std::string file_appender_name = section_name.substr(file_appender_section_prefix.length()); - fc::path file_name = section_tree.get("filename"); - if (file_name.is_relative()) - file_name = fc::absolute(config_ini_filename).parent_path() / file_name; - - - // construct a default file appender config here - // filename will be taken from ini file, everything else hard-coded here - fc::file_appender::config file_appender_config; - file_appender_config.filename = file_name; - file_appender_config.flush = true; - file_appender_config.rotate = true; - file_appender_config.rotation_interval = fc::hours(1); - file_appender_config.rotation_limit = fc::days(1); - logging_config.appenders.push_back(fc::appender_config(file_appender_name, "file", fc::variant(file_appender_config, 20))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, logger_section_prefix)) - { - std::string logger_name = section_name.substr(logger_section_prefix.length()); - std::string level_string = section_tree.get("level"); - std::string appenders_string = section_tree.get("appenders"); - fc::logger_config logger_config(logger_name); - logger_config.level = fc::variant(level_string, 1).as(1); - boost::split(logger_config.appenders, appenders_string, - boost::is_any_of(" ,"), - boost::token_compress_on); - logging_config.loggers.push_back(logger_config); - found_logging_config = true; - } - } - if (found_logging_config) - return logging_config; - else - return fc::optional(); - } - FC_RETHROW_EXCEPTIONS(warn, "") -} diff --git a/programs/delayed_node/CMakeLists.txt b/programs/delayed_node/CMakeLists.txt deleted file mode 100644 index 7e610ace..00000000 --- a/programs/delayed_node/CMakeLists.txt +++ /dev/null @@ -1,21 +0,0 @@ -add_executable( delayed_node main.cpp ) -if( UNIX AND NOT APPLE ) - set(rt_library rt ) -endif() - -find_package( Gperftools QUIET ) -if( GPERFTOOLS_FOUND ) - message( STATUS "Found gperftools; compiling delayed_node with TCMalloc") - list( APPEND PLATFORM_SPECIFIC_LIBS tcmalloc ) -endif() - -target_link_libraries( delayed_node - PRIVATE graphene_app graphene_egenesis_full graphene_delayed_node ${PLATFORM_SPECIFIC_LIBS} ) - -install( TARGETS - delayed_node - - RUNTIME DESTINATION bin - LIBRARY DESTINATION lib - ARCHIVE DESTINATION lib -) diff --git a/programs/delayed_node/main.cpp b/programs/delayed_node/main.cpp deleted file mode 100644 index 3e058b64..00000000 --- a/programs/delayed_node/main.cpp +++ /dev/null @@ -1,305 +0,0 @@ -/* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. - * - * The MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION 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 -#include -#include -#include -#include -#include - -#include - -#include -#include -#include -#include -#include - -#include -#include - -#ifdef WIN32 -# include -#else -# include -#endif - -using namespace graphene; -namespace bpo = boost::program_options; - -void write_default_logging_config_to_stream(std::ostream& out); -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename); - -int main(int argc, char** argv) { - try { - app::application node; - bpo::options_description app_options("Graphene Delayed Node"); - bpo::options_description cfg_options("Graphene Delayed Node"); - app_options.add_options() - ("help,h", "Print this help message and exit.") - ("data-dir,d", bpo::value()->default_value("delayed_node_data_dir"), "Directory containing databases, configuration file, etc.") - ; - - bpo::variables_map options; - - bpo::options_description cli, cfg; - node.set_program_options(cli, cfg); - cfg_options.add(cfg); - - cfg_options.add_options() - ("plugins", bpo::value()->default_value("delayed_node account_history market_history"), - "Space-separated list of plugins to activate"); - - auto delayed_plug = node.register_plugin(); - auto history_plug = node.register_plugin(); - auto market_history_plug = node.register_plugin(); - - // add plugin options to config - try - { - bpo::options_description cli, cfg; - node.set_program_options(cli, cfg); - app_options.add(cli); - cfg_options.add(cfg); - bpo::store(bpo::parse_command_line(argc, argv, app_options), options); - } - catch (const boost::program_options::error& e) - { - std::cerr << "Error parsing command line: " << e.what() << "\n"; - return 1; - } - - if( options.count("help") ) - { - std::cout << app_options << "\n"; - return 0; - } - - fc::path data_dir; - if( options.count("data-dir") ) - { - data_dir = options["data-dir"].as(); - if( data_dir.is_relative() ) - data_dir = fc::current_path() / data_dir; - } - - fc::path config_ini_path = data_dir / "config.ini"; - // Create config file if not already present - if( !fc::exists(config_ini_path) ) - { - ilog("Writing new config file at ${path}", ("path", config_ini_path)); - if( !fc::exists(data_dir) ) - fc::create_directories(data_dir); - - std::ofstream out_cfg(config_ini_path.preferred_string()); - for( const boost::shared_ptr od : cfg_options.options() ) - { - if( !od->description().empty() ) - out_cfg << "# " << od->description() << "\n"; - boost::any store; - if( !od->semantic()->apply_default(store) ) - out_cfg << "# " << od->long_name() << " = \n"; - else { - auto example = od->format_parameter(); - if( example.empty() ) - // This is a boolean switch - out_cfg << od->long_name() << " = " << "false\n"; - else { - // The string is formatted "arg (=)" - example.erase(0, 6); - example.erase(example.length()-1); - out_cfg << od->long_name() << " = " << example << "\n"; - } - } - out_cfg << "\n"; - } - write_default_logging_config_to_stream(out_cfg); - out_cfg.close(); - // read the default logging config we just wrote out to the file and start using it - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - - // Parse configuration file - try { - bpo::store(bpo::parse_config_file(config_ini_path.preferred_string().c_str(), cfg_options, true), options); - // try to get logging options from the config file. - try - { - fc::optional logging_config = load_logging_config_from_ini_file(config_ini_path); - if (logging_config) - fc::configure_logging(*logging_config); - } - catch (const fc::exception&) - { - wlog("Error parsing logging config from config file ${config}, using default config", ("config", config_ini_path.preferred_string())); - } - - bpo::notify(options); - } catch( const boost::program_options::error& e ) { - elog("Error parsing configuration file: ${e}", ("e", e.what())); - return 1; - } - - if( !options.count("plugins") ) - options.insert( std::make_pair( "plugins", bpo::variable_value(std::string("delayed_node account_history market_history"), true) ) ); - - node.initialize(data_dir, options); - node.initialize_plugins( options ); - - node.startup(); - node.startup_plugins(); - - fc::promise::ptr exit_promise = new fc::promise("UNIX Signal Handler"); - fc::set_signal_handler([&exit_promise](int signal) { - exit_promise->set_value(signal); - }, SIGINT); - - ilog("Started delayed node on a chain with ${h} blocks.", ("h", node.chain_database()->head_block_num())); - ilog("Chain ID is ${id}", ("id", node.chain_database()->get_chain_id()) ); - - int signal = exit_promise->wait(); - ilog("Exiting from signal ${n}", ("n", signal)); - node.shutdown_plugins(); - return 0; - } catch( const fc::exception& e ) { - elog("Exiting with error:\n${e}", ("e", e.to_detail_string())); - return 1; - } -} - -// logging config is too complicated to be parsed by boost::program_options, -// so we do it by hand -// -// Currently, you can only specify the filenames and logging levels, which -// are all most users would want to change. At a later time, options can -// be added to control rotation intervals, compression, and other seldom- -// used features -void write_default_logging_config_to_stream(std::ostream& out) -{ - out << "# declare an appender named \"stderr\" that writes messages to the console\n" - "[log.console_appender.stderr]\n" - "stream=std_error\n\n" - "# declare an appender named \"p2p\" that writes messages to p2p.log\n" - "[log.file_appender.p2p]\n" - "filename=logs/p2p/p2p.log\n" - "# filename can be absolute or relative to this config file\n\n" - "# route any messages logged to the default logger to the \"stderr\" logger we\n" - "# declared above, if they are info level are higher\n" - "[logger.default]\n" - "level=info\n" - "appenders=stderr\n\n" - "# route messages sent to the \"p2p\" logger to the p2p appender declared above\n" - "[logger.p2p]\n" - "level=info\n" - "appenders=p2p\n\n"; -} - -fc::optional load_logging_config_from_ini_file(const fc::path& config_ini_filename) -{ - try - { - fc::logging_config logging_config; - bool found_logging_config = false; - - boost::property_tree::ptree config_ini_tree; - boost::property_tree::ini_parser::read_ini(config_ini_filename.preferred_string().c_str(), config_ini_tree); - for (const auto& section : config_ini_tree) - { - const std::string& section_name = section.first; - const boost::property_tree::ptree& section_tree = section.second; - - const std::string console_appender_section_prefix = "log.console_appender."; - const std::string file_appender_section_prefix = "log.file_appender."; - const std::string logger_section_prefix = "logger."; - - if (boost::starts_with(section_name, console_appender_section_prefix)) - { - std::string console_appender_name = section_name.substr(console_appender_section_prefix.length()); - std::string stream_name = section_tree.get("stream"); - - // construct a default console appender config here - // stdout/stderr will be taken from ini file, everything else hard-coded here - fc::console_appender::config console_appender_config; - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::debug, - fc::console_appender::color::green)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::warn, - fc::console_appender::color::brown)); - console_appender_config.level_colors.emplace_back( - fc::console_appender::level_color(fc::log_level::error, - fc::console_appender::color::cyan)); - console_appender_config.stream = fc::variant(stream_name, 1).as(1); - logging_config.appenders.push_back(fc::appender_config(console_appender_name, "console", fc::variant(console_appender_config, GRAPHENE_MAX_NESTED_OBJECTS))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, file_appender_section_prefix)) - { - std::string file_appender_name = section_name.substr(file_appender_section_prefix.length()); - fc::path file_name = section_tree.get("filename"); - if (file_name.is_relative()) - file_name = fc::absolute(config_ini_filename).parent_path() / file_name; - - - // construct a default file appender config here - // filename will be taken from ini file, everything else hard-coded here - fc::file_appender::config file_appender_config; - file_appender_config.filename = file_name; - file_appender_config.flush = true; - file_appender_config.rotate = true; - file_appender_config.rotation_interval = fc::hours(1); - file_appender_config.rotation_limit = fc::days(1); - logging_config.appenders.push_back(fc::appender_config(file_appender_name, "file", fc::variant(file_appender_config, GRAPHENE_MAX_NESTED_OBJECTS))); - found_logging_config = true; - } - else if (boost::starts_with(section_name, logger_section_prefix)) - { - std::string logger_name = section_name.substr(logger_section_prefix.length()); - std::string level_string = section_tree.get("level"); - std::string appenders_string = section_tree.get("appenders"); - fc::logger_config logger_config(logger_name); - logger_config.level = fc::variant(level_string, 1).as(1); - boost::split(logger_config.appenders, appenders_string, - boost::is_any_of(" ,"), - boost::token_compress_on); - logging_config.loggers.push_back(logger_config); - found_logging_config = true; - } - } - if (found_logging_config) - return logging_config; - else - return fc::optional(); - } - FC_RETHROW_EXCEPTIONS(warn, "") -} diff --git a/programs/witness_node/CMakeLists.txt b/programs/witness_node/CMakeLists.txt index 806330d6..d2235034 100644 --- a/programs/witness_node/CMakeLists.txt +++ b/programs/witness_node/CMakeLists.txt @@ -11,7 +11,7 @@ endif() # We have to link against graphene_debug_witness because deficiency in our API infrastructure doesn't allow plugins to be fully abstracted #246 target_link_libraries( witness_node - PRIVATE graphene_app graphene_egenesis_full graphene_snapshot graphene_witness peerplays_sidechain ${PLATFORM_SPECIFIC_LIBS} ) + PRIVATE graphene_app graphene_egenesis_full graphene_snapshot graphene_delayed_node graphene_witness peerplays_sidechain ${PLATFORM_SPECIFIC_LIBS} ) # also add dependencies to graphene_generate_genesis graphene_generate_uia_sharedrop_genesis if you want those plugins install( TARGETS diff --git a/programs/witness_node/main.cpp b/programs/witness_node/main.cpp index c5eea447..b3c0aa37 100644 --- a/programs/witness_node/main.cpp +++ b/programs/witness_node/main.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include @@ -90,6 +91,7 @@ int main(int argc, char** argv) { auto bookie_plug = node->register_plugin(); auto peerplays_sidechain = node->register_plugin(); auto snapshot_plug = node->register_plugin(); + auto delayed_plug = node->register_plugin(); // add plugin options to config try @@ -176,6 +178,7 @@ int main(int argc, char** argv) { node->shutdown_plugins(); node->shutdown(); delete node; + ilog("Witness node is closed and turned off"); return EXIT_SUCCESS; } catch( const fc::exception& e ) { // deleting the node can yield, so do this outside the exception handler diff --git a/tests/cli/cli_fixture.cpp b/tests/cli/cli_fixture.cpp index 70bdfb7c..46192350 100644 --- a/tests/cli/cli_fixture.cpp +++ b/tests/cli/cli_fixture.cpp @@ -231,6 +231,12 @@ signed_block cli_fixture::generate_block(uint32_t skip, const fc::ecc::private_k return block; } +void cli_fixture::generate_blocks( uint32_t block_count ) +{ + for( uint32_t i = 0; i < block_count; ++i ) + generate_block(); +} + bool cli_fixture::generate_maintenance_block() { try { fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("null_key"))); diff --git a/tests/cli/cli_fixture.hpp b/tests/cli/cli_fixture.hpp index 79004f3a..f0486af8 100644 --- a/tests/cli/cli_fixture.hpp +++ b/tests/cli/cli_fixture.hpp @@ -65,6 +65,7 @@ struct cli_fixture int miss_blocks = 0); void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0); + void generate_blocks( uint32_t block_count ); /////////// /// @brief Skip intermediate blocks, and generate a maintenance block diff --git a/tests/cli/son.cpp b/tests/cli/son.cpp index 491ec8f9..ed01be2a 100644 --- a/tests/cli/son.cpp +++ b/tests/cli/son.cpp @@ -39,8 +39,8 @@ public: fixture_(fixture) { fixture_.init_nathan(); - fixture_.generate_blocks(HARDFORK_SON_FOR_HIVE_TIME); - fixture_.generate_block(); + fixture_.generate_blocks(HARDFORK_SON_FOR_ETHEREUM_TIME); + fixture_.generate_maintenance_block(); } void create_son(const std::string& account_name, const std::string& son_url, @@ -128,20 +128,40 @@ BOOST_AUTO_TEST_CASE( create_sons ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; sth.create_son("son1account", "http://son1", sidechain_public_keys); sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; sth.create_son("son2account", "http://son2", sidechain_public_keys); auto son1_obj = con.wallet_api_ptr->get_son("son1account"); BOOST_CHECK(son1_obj.son_account == con.wallet_api_ptr->get_account_id("son1account")); BOOST_CHECK_EQUAL(son1_obj.url, "http://son1"); + BOOST_CHECK_EQUAL(son1_obj.sidechain_public_keys[sidechain_type::bitcoin], "bitcoin_address 1"); + BOOST_CHECK_EQUAL(son1_obj.sidechain_public_keys[sidechain_type::hive], "hive account 1"); + BOOST_CHECK_EQUAL(son1_obj.sidechain_public_keys[sidechain_type::ethereum], "ethereum address 1"); + BOOST_REQUIRE(son1_obj.get_sidechain_vote_id(sidechain_type::bitcoin)); + BOOST_CHECK_EQUAL(son1_obj.get_sidechain_vote_id(sidechain_type::bitcoin)->instance(), 22); + BOOST_REQUIRE(son1_obj.get_sidechain_vote_id(sidechain_type::hive)); + BOOST_CHECK_EQUAL(son1_obj.get_sidechain_vote_id(sidechain_type::hive)->instance(), 23); + BOOST_REQUIRE(son1_obj.get_sidechain_vote_id(sidechain_type::ethereum)); + BOOST_CHECK_EQUAL(son1_obj.get_sidechain_vote_id(sidechain_type::ethereum)->instance(), 24); auto son2_obj = con.wallet_api_ptr->get_son("son2account"); BOOST_CHECK(son2_obj.son_account == con.wallet_api_ptr->get_account_id("son2account")); BOOST_CHECK_EQUAL(son2_obj.url, "http://son2"); + BOOST_CHECK_EQUAL(son2_obj.sidechain_public_keys[sidechain_type::bitcoin], "bitcoin_address 2"); + BOOST_CHECK_EQUAL(son2_obj.sidechain_public_keys[sidechain_type::hive], "hive account 2"); + BOOST_CHECK_EQUAL(son2_obj.sidechain_public_keys[sidechain_type::ethereum], "ethereum address 2"); + BOOST_REQUIRE(son2_obj.get_sidechain_vote_id(sidechain_type::bitcoin)); + BOOST_CHECK_EQUAL(son2_obj.get_sidechain_vote_id(sidechain_type::bitcoin)->instance(), 25); + BOOST_REQUIRE(son2_obj.get_sidechain_vote_id(sidechain_type::hive)); + BOOST_CHECK_EQUAL(son2_obj.get_sidechain_vote_id(sidechain_type::hive)->instance(), 26); + BOOST_REQUIRE(son2_obj.get_sidechain_vote_id(sidechain_type::ethereum)); + BOOST_CHECK_EQUAL(son2_obj.get_sidechain_vote_id(sidechain_type::ethereum)->instance(), 27); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); @@ -162,6 +182,7 @@ BOOST_AUTO_TEST_CASE( cli_update_son ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; son_test_helper sth(*this); sth.create_son("sonmember", "http://sonmember", sidechain_public_keys); @@ -172,15 +193,28 @@ BOOST_AUTO_TEST_CASE( cli_update_son ) auto son_data = con.wallet_api_ptr->get_son("sonmember"); BOOST_CHECK(son_data.url == "http://sonmember"); BOOST_CHECK(son_data.son_account == sonmember_acct.get_id()); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::bitcoin], "bitcoin_address 1"); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::hive], "hive account 1"); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::ethereum], "ethereum address 1"); + BOOST_REQUIRE(son_data.get_sidechain_vote_id(sidechain_type::bitcoin)); + BOOST_CHECK_EQUAL(son_data.get_sidechain_vote_id(sidechain_type::bitcoin)->instance(), 22); + BOOST_REQUIRE(son_data.get_sidechain_vote_id(sidechain_type::hive)); + BOOST_CHECK_EQUAL(son_data.get_sidechain_vote_id(sidechain_type::hive)->instance(), 23); + BOOST_REQUIRE(son_data.get_sidechain_vote_id(sidechain_type::ethereum)); + BOOST_CHECK_EQUAL(son_data.get_sidechain_vote_id(sidechain_type::ethereum)->instance(), 24); // update SON sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated", "", sidechain_public_keys, true); son_data = con.wallet_api_ptr->get_son("sonmember"); BOOST_CHECK(son_data.url == "http://sonmember_updated"); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::bitcoin], "bitcoin_address 2"); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::hive], "hive account 2"); + BOOST_CHECK_EQUAL(son_data.sidechain_public_keys[sidechain_type::ethereum], "ethereum address 2"); // update SON signing key sidechain_public_keys.clear(); @@ -208,21 +242,28 @@ BOOST_AUTO_TEST_CASE( son_voting ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; sth.create_son("son1account", "http://son1", sidechain_public_keys); sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; sth.create_son("son2account", "http://son2", sidechain_public_keys); + BOOST_CHECK(generate_maintenance_block()); + BOOST_TEST_MESSAGE("Voting for SONs"); son_object son1_obj; son_object son2_obj; signed_transaction vote_son1_tx; signed_transaction vote_son2_tx; - uint64_t son1_start_votes, son1_end_votes; - uint64_t son2_start_votes, son2_end_votes; + flat_map son1_start_votes, son1_end_votes; + flat_map son2_start_votes, son2_end_votes; + + //! Get nathan account + const auto nathan_account_object = con.wallet_api_ptr->get_account("nathan"); son1_obj = con.wallet_api_ptr->get_son("son1account"); son1_start_votes = son1_obj.total_votes; @@ -232,85 +273,120 @@ BOOST_AUTO_TEST_CASE( son_voting ) con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); // Vote for a son1account BOOST_TEST_MESSAGE("Voting for son1account"); - vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", true, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::bitcoin, true, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::hive, true, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::ethereum, true, true); BOOST_CHECK(generate_maintenance_block()); // Verify that the vote is there son1_obj = con.wallet_api_ptr->get_son("son1account"); son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes > son1_start_votes); + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] > son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] > son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] > son1_start_votes[sidechain_type::ethereum]); // Vote for a son2account BOOST_TEST_MESSAGE("Voting for son2account"); - vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", true, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::bitcoin, true, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::hive, true, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::ethereum, true, true); BOOST_CHECK(generate_maintenance_block()); // Verify that the vote is there son2_obj = con.wallet_api_ptr->get_son("son2account"); son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes > son2_start_votes); - - //! Get nathan account - const auto nathan_account_object = con.wallet_api_ptr->get_account("nathan"); + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] > son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] > son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] > son2_start_votes[sidechain_type::ethereum]); //! Check son1account voters - auto voters_for_son1account = con.wallet_api_ptr->get_voters("son1account"); - BOOST_REQUIRE(voters_for_son1account.voters_for_son); - BOOST_CHECK_EQUAL(voters_for_son1account.voters_for_son->voters.size(), 1); - BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account.voters_for_son->voters[0].instance, nathan_account_object.id.instance()); + auto voters_for_son1account = con.wallet_api_ptr->get_voters("son1account").voters_for_son; + BOOST_REQUIRE(voters_for_son1account); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::bitcoin).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::bitcoin).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::hive).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::hive).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::ethereum).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::ethereum).voters[0].instance, nathan_account_object.id.instance()); //! Check son2account voters - auto voters_for_son2account = con.wallet_api_ptr->get_voters("son2account"); - BOOST_REQUIRE(voters_for_son2account.voters_for_son); - BOOST_CHECK_EQUAL(voters_for_son2account.voters_for_son->voters.size(), 1); - BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account.voters_for_son->voters[0].instance, nathan_account_object.id.instance()); + auto voters_for_son2account = con.wallet_api_ptr->get_voters("son2account").voters_for_son; + BOOST_REQUIRE(voters_for_son2account); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::bitcoin).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::bitcoin).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::hive).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::hive).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::ethereum).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::ethereum).voters[0].instance, nathan_account_object.id.instance()); //! Check votes of nathan - auto nathan_votes = con.wallet_api_ptr->get_votes("nathan"); - BOOST_REQUIRE(nathan_votes.votes_for_sons); - BOOST_CHECK_EQUAL(nathan_votes.votes_for_sons->size(), 2); - BOOST_CHECK_EQUAL(nathan_votes.votes_for_sons->at(0).id.instance(), son1_obj.id.instance()); - BOOST_CHECK_EQUAL(nathan_votes.votes_for_sons->at(1).id.instance(), son2_obj.id.instance()); + auto nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(nathan_votes_for_son); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(1).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(1).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(1).id.instance(), son2_obj.id.instance()); // Withdraw vote for a son1account BOOST_TEST_MESSAGE("Withdraw vote for a son1account"); - vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", false, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::bitcoin, false, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::hive, false, true); + vote_son1_tx = con.wallet_api_ptr->vote_for_son("nathan", "son1account", sidechain_type::ethereum, false, true); BOOST_CHECK(generate_maintenance_block()); // Verify that the vote is removed son1_obj = con.wallet_api_ptr->get_son("son1account"); son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes == son1_start_votes); + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] == son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] == son1_start_votes[sidechain_type::ethereum]); //! Check son1account voters - voters_for_son1account = con.wallet_api_ptr->get_voters("son1account"); - BOOST_REQUIRE(voters_for_son1account.voters_for_son); - BOOST_CHECK_EQUAL(voters_for_son1account.voters_for_son->voters.size(), 0); + voters_for_son1account = con.wallet_api_ptr->get_voters("son1account").voters_for_son; + BOOST_REQUIRE(voters_for_son1account); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::bitcoin).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::hive).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::ethereum).voters.size(), 0); //! Check votes of nathan - nathan_votes = con.wallet_api_ptr->get_votes("nathan"); - BOOST_REQUIRE(nathan_votes.votes_for_sons); - BOOST_CHECK_EQUAL(nathan_votes.votes_for_sons->size(), 1); - BOOST_CHECK_EQUAL(nathan_votes.votes_for_sons->at(0).id.instance(), son2_obj.id.instance()); + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(nathan_votes_for_son); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(0).id.instance(), son2_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(0).id.instance(), son2_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(0).id.instance(), son2_obj.id.instance()); // Withdraw vote for a son2account BOOST_TEST_MESSAGE("Withdraw vote for a son2account"); - vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", false, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::bitcoin, false, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::hive, false, true); + vote_son2_tx = con.wallet_api_ptr->vote_for_son("nathan", "son2account", sidechain_type::ethereum, false, true); BOOST_CHECK(generate_maintenance_block()); // Verify that the vote is removed son2_obj = con.wallet_api_ptr->get_son("son2account"); son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes == son2_start_votes); + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] == son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] == son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] == son2_start_votes[sidechain_type::ethereum]); //! Check son2account voters - voters_for_son2account = con.wallet_api_ptr->get_voters("son2account"); - BOOST_REQUIRE(voters_for_son2account.voters_for_son); - BOOST_CHECK_EQUAL(voters_for_son2account.voters_for_son->voters.size(), 0); + voters_for_son2account = con.wallet_api_ptr->get_voters("son2account").voters_for_son; + BOOST_REQUIRE(voters_for_son2account); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::bitcoin).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::hive).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::ethereum).voters.size(), 0); //! Check votes of nathan - nathan_votes = con.wallet_api_ptr->get_votes("nathan"); - BOOST_CHECK(!nathan_votes.votes_for_sons.valid()); + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_CHECK(!nathan_votes_for_son); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); @@ -337,7 +413,8 @@ BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) global_property_object gpo; gpo = con.wallet_api_ptr->get_global_properties(); - unsigned int son_number = gpo.parameters.maximum_son_count(); + //! Set son number as 5 (as the begining son count) + unsigned int son_number = 5; flat_map sidechain_public_keys; @@ -347,6 +424,7 @@ BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); sth.create_son("sonaccount" + fc::to_pretty_string(i), "http://son" + fc::to_pretty_string(i), sidechain_public_keys, @@ -362,7 +440,9 @@ BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); std::string name = "sonaccount" + fc::to_pretty_string(i); - vote_tx = con.wallet_api_ptr->vote_for_son(name, name, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, sidechain_type::ethereum, true, true); } BOOST_CHECK(generate_maintenance_block()); @@ -370,37 +450,55 @@ BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) { std::string name1 = "sonaccount" + fc::to_pretty_string(i); std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); - vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::ethereum, true, true); } gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_TEST_MESSAGE("gpo active_sons[bitcoin]: " << gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_TEST_MESSAGE("gpo active_sons[hive]: " << gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_TEST_MESSAGE("gpo active_sons[ethereum]: " << gpo.active_sons.at(sidechain_type::ethereum).size()); BOOST_CHECK(generate_maintenance_block()); gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_TEST_MESSAGE("gpo active_sons[bitcoin]: " << gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_TEST_MESSAGE("gpo active_sons[hive]: " << gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_TEST_MESSAGE("gpo active_sons[ethereum]: " << gpo.active_sons.at(sidechain_type::ethereum).size()); for(unsigned int i = 0; i < son_number - 1; i++) { std::string name1 = "sonaccount" + fc::to_pretty_string(i + 2); std::string name2 = "sonaccount" + fc::to_pretty_string(i); - vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::ethereum, true, true); } gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_TEST_MESSAGE("gpo active_sons[bitcoin]: " << gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_TEST_MESSAGE("gpo active_sons[hive]: " << gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_TEST_MESSAGE("gpo active_sons[ethereum]: " << gpo.active_sons.at(sidechain_type::ethereum).size()); BOOST_CHECK(generate_maintenance_block()); gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_TEST_MESSAGE("gpo active_sons[bitcoin]: " << gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_TEST_MESSAGE("gpo active_sons[hive]: " << gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_TEST_MESSAGE("gpo active_sons[ethereum]: " << gpo.active_sons.at(sidechain_type::ethereum).size()); for(unsigned int i = 0; i < son_number - 2; i++) { std::string name1 = "sonaccount" + fc::to_pretty_string(i + 3); std::string name2 = "sonaccount" + fc::to_pretty_string(i); - vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, sidechain_type::ethereum, true, true); } gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_TEST_MESSAGE("gpo active_sons[bitcoin]: " << gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_TEST_MESSAGE("gpo active_sons[hive]: " << gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_TEST_MESSAGE("gpo active_sons[ethereum]: " << gpo.active_sons.at(sidechain_type::ethereum).size()); BOOST_CHECK(generate_maintenance_block()); - BOOST_CHECK(gpo.active_sons.size() == gpo.parameters.maximum_son_count()); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::bitcoin).size() == son_number); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::hive).size() == son_number); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::ethereum).size() == son_number); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); @@ -422,11 +520,13 @@ BOOST_AUTO_TEST_CASE( list_son ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; sth.create_son("son1account", "http://son1", sidechain_public_keys); sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; sth.create_son("son2account", "http://son2", sidechain_public_keys); auto res = con.wallet_api_ptr->list_sons("", 100); @@ -444,9 +544,9 @@ BOOST_AUTO_TEST_CASE( list_son ) BOOST_AUTO_TEST_CASE( update_son_votes_test ) { - BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests begin"); - try - { + BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests begin"); + try + { flat_map sidechain_public_keys; son_test_helper sth(*this); @@ -454,150 +554,296 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; sth.create_son("son1account", "http://son1", sidechain_public_keys); sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; sth.create_son("son2account", "http://son2", sidechain_public_keys); - BOOST_TEST_MESSAGE("Vote for 2 accounts with update_son_votes"); + BOOST_CHECK(generate_maintenance_block()); - son_object son1_obj; - son_object son2_obj; - uint64_t son1_start_votes, son1_end_votes; - uint64_t son2_start_votes, son2_end_votes; + BOOST_TEST_MESSAGE("Vote for 2 accounts with update_son_votes"); - // Get votes at start - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_start_votes = son1_obj.total_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_start_votes = son2_obj.total_votes; + son_object son1_obj; + son_object son2_obj; + flat_map son1_start_votes, son1_end_votes; + flat_map son2_start_votes, son2_end_votes; - std::vector accepted; - std::vector rejected; - signed_transaction update_votes_tx; + //! Get nathan account + const auto nathan_account_object = con.wallet_api_ptr->get_account("nathan"); - // Vote for both SONs - accepted.clear(); - rejected.clear(); - accepted.push_back("son1account"); - accepted.push_back("son2account"); - con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); - update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 2, true); - generate_block(); - BOOST_CHECK(generate_maintenance_block()); + // Get votes at start + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_start_votes = son1_obj.total_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_start_votes = son2_obj.total_votes; - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes > son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes > son2_start_votes); - son2_start_votes = son2_end_votes; + std::vector accepted; + std::vector rejected; + signed_transaction update_votes_tx; + // Vote for both SONs + accepted.clear(); + rejected.clear(); + accepted.push_back("son1account"); + accepted.push_back("son2account"); + con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 2, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 2, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 2, true); + generate_block(); + BOOST_CHECK(generate_maintenance_block()); - // Withdraw vote for SON 1 - accepted.clear(); - rejected.clear(); - rejected.push_back("son1account"); - con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); - update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 1, true); - BOOST_CHECK(generate_maintenance_block()); + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] > son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] > son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] > son1_start_votes[sidechain_type::ethereum]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] > son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] > son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] > son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes < son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - // voice distribution changed, SON2 now has all voices - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK((son2_end_votes > son2_start_votes)); // nathan spent funds for vb, it has different voting power - son2_start_votes = son2_end_votes; + //! Check son1account voters + auto voters_for_son1account = con.wallet_api_ptr->get_voters("son1account").voters_for_son; + BOOST_REQUIRE(voters_for_son1account); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::bitcoin).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::bitcoin).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::hive).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::hive).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son1account->at(sidechain_type::ethereum).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son1account->at(sidechain_type::ethereum).voters[0].instance, nathan_account_object.id.instance()); - // Try to reject incorrect SON - accepted.clear(); - rejected.clear(); - rejected.push_back("son1accnt"); - BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 1, true), fc::exception); - generate_block(); + //! Check son2account voters + auto voters_for_son2account = con.wallet_api_ptr->get_voters("son2account").voters_for_son; + BOOST_REQUIRE(voters_for_son2account); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::bitcoin).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::bitcoin).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::hive).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::hive).voters[0].instance, nathan_account_object.id.instance()); + BOOST_REQUIRE_EQUAL(voters_for_son2account->at(sidechain_type::ethereum).voters.size(), 1); + BOOST_CHECK_EQUAL((uint32_t)voters_for_son2account->at(sidechain_type::ethereum).voters[0].instance, nathan_account_object.id.instance()); - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes == son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes == son2_start_votes); - son2_start_votes = son2_end_votes; + //! Check votes of nathan + auto nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(nathan_votes_for_son); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(1).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(1).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).size(), 2); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(0).id.instance(), son1_obj.id.instance()); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(1).id.instance(), son2_obj.id.instance()); - // Reject SON2 - accepted.clear(); - rejected.clear(); - rejected.push_back("son2account"); - update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 0, true); - BOOST_CHECK(generate_maintenance_block()); + // Withdraw vote for SON 1 + accepted.clear(); + rejected.clear(); + rejected.push_back("son1account"); + con.wallet_api_ptr->create_vesting_balance("nathan", "1000", "1.3.0", vesting_balance_type::gpos, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 1, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 1, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 1, true); + BOOST_CHECK(generate_maintenance_block()); - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes == son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes < son2_start_votes); - son2_start_votes = son2_end_votes; + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] < son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] < son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] < son1_start_votes[sidechain_type::ethereum]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + // voice distribution changed, SON2 now has all voices + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] > son2_start_votes[sidechain_type::bitcoin]); // nathan spent funds for vb, it has different voting power + BOOST_CHECK(son2_end_votes[sidechain_type::hive] > son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] > son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; - // Try to accept and reject the same SON - accepted.clear(); - rejected.clear(); - rejected.push_back("son1accnt"); - accepted.push_back("son1accnt"); - BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 1, true), fc::exception); - BOOST_CHECK(generate_maintenance_block()); + //! Check son1account voters + voters_for_son1account = con.wallet_api_ptr->get_voters("son1account").voters_for_son; + BOOST_REQUIRE(voters_for_son1account); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::bitcoin).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::hive).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son1account->at(sidechain_type::ethereum).voters.size(), 0); - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes == son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes == son2_start_votes); - son2_start_votes = son2_end_votes; + //! Check votes of nathan + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(nathan_votes_for_son); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(0).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(0).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(0).id.instance(), son2_obj.id.instance()); - // Try to accept and reject empty lists - accepted.clear(); - rejected.clear(); - BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 1, true), fc::exception); - BOOST_CHECK(generate_maintenance_block()); + // Try to reject incorrect SON + accepted.clear(); + rejected.clear(); + rejected.push_back("son1accnt"); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 1, true), fc::exception); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 1, true), fc::exception); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 1, true), fc::exception); + generate_block(); - // Verify the votes - son1_obj = con.wallet_api_ptr->get_son("son1account"); - son1_end_votes = son1_obj.total_votes; - BOOST_CHECK(son1_end_votes == son1_start_votes); - son1_start_votes = son1_end_votes; - son2_obj = con.wallet_api_ptr->get_son("son2account"); - son2_end_votes = son2_obj.total_votes; - BOOST_CHECK(son2_end_votes == son2_start_votes); - son2_start_votes = son2_end_votes; + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] == son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] == son1_start_votes[sidechain_type::ethereum]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] == son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] == son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] == son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; - } catch( fc::exception& e ) { - BOOST_TEST_MESSAGE("SON cli wallet tests exception"); - edump((e.to_detail_string())); - throw; - } - BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests end"); + //! Check votes of nathan + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(nathan_votes_for_son); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::bitcoin).at(0).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::hive).at(0).id.instance(), son2_obj.id.instance()); + BOOST_REQUIRE_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).size(), 1); + BOOST_CHECK_EQUAL(nathan_votes_for_son->at(sidechain_type::ethereum).at(0).id.instance(), son2_obj.id.instance()); + + // Reject SON2 + accepted.clear(); + rejected.clear(); + rejected.push_back("son2account"); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 0, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 0, true); + update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 0, true); + BOOST_CHECK(generate_maintenance_block()); + + // Vote for less SONs than num_son (2 votes, but num_son is 3) + accepted.clear(); + rejected.clear(); + accepted.push_back("son1account"); + accepted.push_back("son2account"); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 3, true), fc::exception); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 3, true), fc::exception); + BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 3, true), fc::exception); + generate_block(); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] == son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] == son1_start_votes[sidechain_type::ethereum]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] < son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] < son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] < son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; + + //! Check son2account voters + voters_for_son2account = con.wallet_api_ptr->get_voters("son2account").voters_for_son; + BOOST_REQUIRE(voters_for_son2account); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::bitcoin).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::hive).voters.size(), 0); + BOOST_CHECK_EQUAL(voters_for_son2account->at(sidechain_type::ethereum).voters.size(), 0); + + //! Check votes of nathan + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(!nathan_votes_for_son); + + // Try to accept and reject the same SON + accepted.clear(); + rejected.clear(); + rejected.push_back("son1accnt"); + accepted.push_back("son1accnt"); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 1, true), fc::exception); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 1, true), fc::exception); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 1, true), fc::exception); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] == son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::ethereum] == son1_start_votes[sidechain_type::ethereum]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] == son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] == son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] == son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; + + //! Check votes of nathan + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(!nathan_votes_for_son); + + // Try to accept and reject empty lists + accepted.clear(); + rejected.clear(); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::bitcoin, 1, true), fc::exception); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::hive, 1, true), fc::exception); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, rejected, + sidechain_type::ethereum, 1, true), fc::exception); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son1_end_votes[sidechain_type::hive] == son1_start_votes[sidechain_type::hive]); + BOOST_CHECK(son1_end_votes[sidechain_type::bitcoin] == son1_start_votes[sidechain_type::bitcoin]); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes[sidechain_type::bitcoin] == son2_start_votes[sidechain_type::bitcoin]); + BOOST_CHECK(son2_end_votes[sidechain_type::hive] == son2_start_votes[sidechain_type::hive]); + BOOST_CHECK(son2_end_votes[sidechain_type::ethereum] == son2_start_votes[sidechain_type::ethereum]); + son2_start_votes = son2_end_votes; + + //! Check votes of nathan + nathan_votes_for_son = con.wallet_api_ptr->get_votes("nathan").votes_for_sons; + BOOST_REQUIRE(!nathan_votes_for_son); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests end"); } BOOST_AUTO_TEST_CASE( related_functions ) @@ -606,7 +852,9 @@ BOOST_AUTO_TEST_CASE( related_functions ) try { global_property_object gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_CHECK(gpo.active_sons.size() == 0); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::bitcoin).size() == 0); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::hive).size() == 0); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::ethereum).size() == 0); flat_map sidechain_public_keys; @@ -615,15 +863,19 @@ BOOST_AUTO_TEST_CASE( related_functions ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 1"; sidechain_public_keys[sidechain_type::hive] = "hive account 1"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 1"; sth.create_son("son1account", "http://son1", sidechain_public_keys); sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address 2"; sidechain_public_keys[sidechain_type::hive] = "hive account 2"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address 2"; sth.create_son("son2account", "http://son2", sidechain_public_keys); gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_CHECK(gpo.active_sons.size() == 2); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::bitcoin).size() == 2); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::hive).size() == 2); + BOOST_CHECK(gpo.active_sons.at(sidechain_type::ethereum).size() == 2); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); @@ -633,9 +885,9 @@ BOOST_AUTO_TEST_CASE( related_functions ) BOOST_TEST_MESSAGE("SON-related functions cli wallet tests end"); } -BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) +BOOST_FIXTURE_TEST_CASE( get_active_sons, cli_fixture ) { - BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons begin"); + BOOST_TEST_MESSAGE("SON cli wallet tests for get_active_sons begin"); try { son_test_helper sth(*this); @@ -644,16 +896,22 @@ BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) global_property_object gpo; gpo = con.wallet_api_ptr->get_global_properties(); - unsigned int son_number = gpo.parameters.maximum_son_count(); + unsigned int son_number = 15; //gpo.parameters.maximum_son_count(); flat_map sidechain_public_keys; - + BOOST_TEST_MESSAGE("Verify that there are no sons"); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::ethereum).size()); + auto gpo_active_sons = gpo.active_sons; // create son accounts - for(unsigned int i = 0; i < son_number + 1; i++) + BOOST_TEST_MESSAGE("Create son accounts"); + for(unsigned int i = 1; i < son_number + 1; i++) { sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); sth.create_son("sonaccount" + fc::to_pretty_string(i), "http://son" + fc::to_pretty_string(i), sidechain_public_keys, @@ -663,42 +921,674 @@ BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); } BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); - BOOST_TEST_MESSAGE("Voting for SONs"); - for(unsigned int i = 1; i < son_number + 1; i++) - { - std::string name = "sonaccount" + fc::to_pretty_string(i); - vote_tx = con.wallet_api_ptr->vote_for_son(name, name, true, true); - } - BOOST_CHECK(generate_maintenance_block()); + gpo_active_sons = gpo.active_sons; + auto cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); - for(unsigned int i = 1; i < son_number; i++) + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 5; i < son_number - 1; i++) { - std::string name1 = "sonaccount" + fc::to_pretty_string(i); - std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); - vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, true, true); } BOOST_CHECK(generate_maintenance_block()); gpo = con.wallet_api_ptr->get_global_properties(); - BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); - BOOST_CHECK(gpo.active_sons.size() == son_number); + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); - map active_sons = con.wallet_api_ptr->list_active_sons(); - BOOST_CHECK(active_sons.size() == son_number); - for(unsigned int i = 1; i < son_number + 1; i++) + BOOST_TEST_MESSAGE("Unvoting for Specific SON"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::ethereum, false, true); + + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + BOOST_TEST_MESSAGE("Unvoting for Specific SON in Specific Sidechain"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount6", "sonaccount7", sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount7", "sonaccount8", sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount8", "sonaccount9", sidechain_type::ethereum, false, true); + + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + BOOST_TEST_MESSAGE("Unvoting for all SONs"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount6", "sonaccount7", sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount7", "sonaccount8", sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount8", "sonaccount9", sidechain_type::ethereum, true, true); + BOOST_CHECK(generate_maintenance_block()); + + for(unsigned int i = 6; i < son_number - 1; i++) { - std::string name = "sonaccount" + fc::to_pretty_string(i); - BOOST_CHECK(active_sons.find(name) != active_sons.end()); + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, false, true); } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->get_active_sons()); - BOOST_CHECK_NO_THROW(con.wallet_api_ptr->list_active_sons()); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); edump((e.to_detail_string())); throw; } - BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons end"); + BOOST_TEST_MESSAGE("SON cli wallet tests for get_active_sons end"); +} + +BOOST_FIXTURE_TEST_CASE( get_active_sons_by_sidechain, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON cli wallet tests for get_active_sons_by_sidechain begin"); + try + { + son_test_helper sth(*this); + + signed_transaction vote_tx; + global_property_object gpo; + + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = 15; + + flat_map sidechain_public_keys; + BOOST_TEST_MESSAGE("Verify that there are no sons"); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::bitcoin).size()); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::hive).size()); + BOOST_CHECK(0 == gpo.active_sons.at(sidechain_type::ethereum).size()); + + auto gpo_active_sons = gpo.active_sons; + // create son accounts + BOOST_TEST_MESSAGE("Create son accounts"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + auto cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + auto cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 5; i < son_number - 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_TEST_MESSAGE("Unvoting for Specific SON"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount5", "sonaccount6", sidechain_type::ethereum, false, true); + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_TEST_MESSAGE("Unvoting for Specific SON in Specific Sidechain"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount6", "sonaccount7", sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount7", "sonaccount8", sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount8", "sonaccount9", sidechain_type::ethereum, false, true); + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_TEST_MESSAGE("Unvoting for all SONs"); + + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount6", "sonaccount7", sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount7", "sonaccount8", sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son("sonaccount8", "sonaccount9", sidechain_type::ethereum, true, true); + BOOST_CHECK(generate_maintenance_block()); + + for(unsigned int i = 6; i < son_number - 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + if(i == 6) + { + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, false, true); + } + else if(i == 7) + { + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, false, true); + } + else if(i == 8) + { + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, false, true); + } + else{ + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, false, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, false, true); + } + } + + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_CHECK(gpo.active_sons != gpo_active_sons); + + gpo_active_sons = gpo.active_sons; + cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->get_active_sons_by_sidechain(sidechain_type::bitcoin)); + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->get_active_sons_by_sidechain(sidechain_type::hive)); + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->get_active_sons_by_sidechain(sidechain_type::ethereum)); + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON cli wallet tests for get_active_sons_by_sidechain end"); +} + +BOOST_FIXTURE_TEST_CASE( get_son_network_status, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON get_son_network_status cli wallet tests begin"); + try + { + son_test_helper sth(*this); + + auto db = app1->chain_database(); + signed_transaction vote_tx; + + global_property_object gpo; + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count(); + BOOST_TEST_MESSAGE("son_number"< sidechain_public_keys; + + // create son accounts + for(unsigned int i = 1; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + + } + BOOST_CHECK(generate_maintenance_block()); + + auto network_status_obj = con.wallet_api_ptr->get_son_network_status(); + + for(map>::iterator outer_iter=network_status_obj.begin(); outer_iter!=network_status_obj.end(); ++outer_iter) + { + for(map::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "No heartbeats sent"); + } + } + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number-1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 2); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + auto gpo_active_sons = gpo.active_sons; + auto cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + BOOST_TEST_MESSAGE("Sending Heartbeat for sonaccount3"); + son_object son_obj1 = con.wallet_api_ptr->get_son("sonaccount3"); + signed_transaction trx1; + son_heartbeat_operation op1; + op1.owner_account = son_obj1.son_account; + op1.son_id = son_obj1.id; + op1.ts = db->head_block_time()+fc::seconds(2*db->block_interval()); + trx1.operations.push_back(op1); + con.wallet_api_ptr->sign_transaction(trx1, true); + + generate_blocks(50); + + BOOST_TEST_MESSAGE("Checking Network Status"); + network_status_obj = con.wallet_api_ptr->get_son_network_status(); + for(map>::iterator outer_iter=network_status_obj.begin(); outer_iter!=network_status_obj.end(); ++outer_iter) + { + for(map::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) + { + if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "OK, regular SON heartbeat"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "No heartbeats sent"); + } + } + } + + BOOST_TEST_MESSAGE("Sending Heartbeat for sonaccount4"); + + son_object son_obj2 = con.wallet_api_ptr->get_son("sonaccount4"); + signed_transaction trx2; + son_heartbeat_operation op2; + op2.owner_account = son_obj2.son_account; + op2.son_id = son_obj2.id; + op2.ts = db->head_block_time()+fc::seconds(2*db->block_interval()); + trx2.operations.push_back(op2); + con.wallet_api_ptr->sign_transaction(trx2, true); + + generate_blocks(50); + + BOOST_TEST_MESSAGE("Checking Network Status"); + network_status_obj = con.wallet_api_ptr->get_son_network_status(); + for(map>::iterator outer_iter=network_status_obj.begin(); outer_iter!=network_status_obj.end(); ++outer_iter) + { + for(map::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) + { + if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "OK, irregular SON heartbeat, but not triggering SON down proposal"); + } + else if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "OK, regular SON heartbeat"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "No heartbeats sent"); + } + } + } + + generate_blocks(db->head_block_time() + gpo.parameters.son_heartbeat_frequency(), false); + BOOST_TEST_MESSAGE("Checking Network Status"); + network_status_obj = con.wallet_api_ptr->get_son_network_status(); + for(map>::iterator outer_iter=network_status_obj.begin(); outer_iter!=network_status_obj.end(); ++outer_iter) + { + for(map::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) + { + if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "OK, irregular SON heartbeat, but not triggering SON down proposal"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "No heartbeats sent"); + } + } + } + + generate_blocks(db->head_block_time() + gpo.parameters.son_heartbeat_frequency() + gpo.parameters.son_down_time(), false); + BOOST_TEST_MESSAGE("Checking Network Status"); + + network_status_obj = con.wallet_api_ptr->get_son_network_status(); + for(map>::iterator outer_iter=network_status_obj.begin(); outer_iter!=network_status_obj.end(); ++outer_iter) + { + for(map::iterator inner_iter=outer_iter->second.begin(); inner_iter!=outer_iter->second.end(); ++inner_iter) + { + if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else if((inner_iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (inner_iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< inner_iter->second); + BOOST_CHECK(inner_iter->second == "No heartbeats sent"); + } + } + } + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON get_son_network_status cli wallet tests end"); +} + +BOOST_FIXTURE_TEST_CASE( get_son_network_status_by_sidechain, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON get_son_network_status_by_sidechain cli wallet tests begin"); + try + { + son_test_helper sth(*this); + signed_transaction vote_tx; + auto db = app1->chain_database(); + + global_property_object gpo; + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count(); + BOOST_TEST_MESSAGE("son_number"< sidechain_public_keys; + + // create son accounts + for(unsigned int i = 1; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + con.wallet_api_ptr->transfer( + "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); + con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); + + } + + // Check Network Status Before sending Heartbeats + BOOST_CHECK(generate_maintenance_block()); + for(sidechain_type sidechain : all_sidechain_types) + { + auto network_status_obj = con.wallet_api_ptr->get_son_network_status_by_sidechain(sidechain); + for(map::iterator iter=network_status_obj.begin(); iter!=network_status_obj.end(); ++iter) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "No heartbeats sent"); + } + } + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number-1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 2); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::bitcoin, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::hive, true, true); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name2, sidechain_type::ethereum, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + + auto gpo_active_sons = gpo.active_sons; + auto cmd_active_sons = con.wallet_api_ptr->get_active_sons(); + BOOST_CHECK(gpo_active_sons == cmd_active_sons); + + auto cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::bitcoin)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::bitcoin) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::ethereum)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::ethereum) == cmd_active_sons2); + + cmd_active_sons2 = con.wallet_api_ptr->get_active_sons_by_sidechain((sidechain_type::hive)); + BOOST_CHECK(gpo_active_sons.at(sidechain_type::hive) == cmd_active_sons2); + + BOOST_TEST_MESSAGE("Sending Heartbeat for sonaccount3"); + son_object son_obj1 = con.wallet_api_ptr->get_son("sonaccount3"); + signed_transaction trx1; + son_heartbeat_operation op1; + op1.owner_account = son_obj1.son_account; + op1.son_id = son_obj1.id; + op1.ts = db->head_block_time()+fc::seconds(2*db->block_interval()); + trx1.operations.push_back(op1); + con.wallet_api_ptr->sign_transaction(trx1, true); + + generate_blocks(50); + + BOOST_TEST_MESSAGE("Checking Network Status"); + for(sidechain_type sidechain : all_sidechain_types) + { + auto network_status_obj = con.wallet_api_ptr->get_son_network_status_by_sidechain(sidechain); + for(map::iterator iter=network_status_obj.begin(); iter!=network_status_obj.end(); ++iter) + { + if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "OK, regular SON heartbeat"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "No heartbeats sent"); + } + + } + } + + BOOST_TEST_MESSAGE("Sending Heartbeat for sonaccount4"); + son_object son_obj2 = con.wallet_api_ptr->get_son("sonaccount4"); + signed_transaction trx2; + son_heartbeat_operation op2; + op2.owner_account = son_obj2.son_account; + op2.son_id = son_obj2.id; + op2.ts = db->head_block_time()+fc::seconds(2*db->block_interval()); + trx2.operations.push_back(op2); + con.wallet_api_ptr->sign_transaction(trx2, true); + + generate_blocks(50); + for(sidechain_type sidechain : all_sidechain_types) + { + auto network_status_obj = con.wallet_api_ptr->get_son_network_status_by_sidechain(sidechain); + for(map::iterator iter=network_status_obj.begin(); iter!=network_status_obj.end(); ++iter) + { + if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "OK, irregular SON heartbeat, but not triggering SON down proposal"); + } + else if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "OK, regular SON heartbeat"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "No heartbeats sent"); + } + } + } + + generate_blocks(db->head_block_time() + gpo.parameters.son_heartbeat_frequency(), false); + BOOST_TEST_MESSAGE("Checking Network Status"); + for(sidechain_type sidechain : all_sidechain_types) + { + auto network_status_obj = con.wallet_api_ptr->get_son_network_status_by_sidechain(sidechain); + for(map::iterator iter=network_status_obj.begin(); iter!=network_status_obj.end(); ++iter) + { + if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "OK, irregular SON heartbeat, but not triggering SON down proposal"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "No heartbeats sent"); + } + } + } + + generate_blocks(db->head_block_time() + gpo.parameters.son_heartbeat_frequency() + gpo.parameters.son_down_time(), false);; + BOOST_TEST_MESSAGE("Checking Network Status"); + for(sidechain_type sidechain : all_sidechain_types) + { + auto network_status_obj = con.wallet_api_ptr->get_son_network_status_by_sidechain(sidechain); + for(map::iterator iter=network_status_obj.begin(); iter!=network_status_obj.end(); ++iter) + { + if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(0).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(0).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else if((iter->first == gpo.active_sons.at(sidechain_type::bitcoin).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::hive).at(1).son_id) && + (iter->first == gpo.active_sons.at(sidechain_type::ethereum).at(1).son_id)) + { + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "NOT OK, irregular SON heartbeat, triggering SON down proposal"); + } + else{ + BOOST_TEST_MESSAGE("status: "<< iter->second); + BOOST_CHECK(iter->second == "No heartbeats sent"); + } + } + } + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON get_son_network_status_by_sidechain cli wallet tests end"); } BOOST_AUTO_TEST_CASE( maintenance_test ) @@ -722,6 +1612,7 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) sidechain_public_keys.clear(); sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); sidechain_public_keys[sidechain_type::hive] = "hive account " + fc::to_pretty_string(i); + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address " + fc::to_pretty_string(i); sth.create_son("sonaccount" + fc::to_pretty_string(i), "http://son" + fc::to_pretty_string(i), sidechain_public_keys, @@ -735,12 +1626,16 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) con.wallet_api_ptr->transfer( "nathan", "sonaccount" + fc::to_pretty_string(i), "1000", "1.3.0", "Here are some CORE tokens for your new account", true ); con.wallet_api_ptr->create_vesting_balance("sonaccount" + fc::to_pretty_string(i), "500", "1.3.0", vesting_balance_type::gpos, true); - con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, true, true); + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, sidechain_type::bitcoin, true, true); + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, sidechain_type::hive, true, true); + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, sidechain_type::ethereum, true, true); } BOOST_CHECK(generate_maintenance_block()); son_object son_obj = con.wallet_api_ptr->get_son(name); - BOOST_CHECK(son_obj.status == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::bitcoin) == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::hive) == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::ethereum) == son_status::active); // put SON in maintenance mode con.wallet_api_ptr->request_son_maintenance(name, true); @@ -748,7 +1643,9 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) // check SON is in request_maintenance son_obj = con.wallet_api_ptr->get_son(name); - BOOST_CHECK(son_obj.status == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::bitcoin) == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::hive) == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::ethereum) == son_status::request_maintenance); // restore SON activity con.wallet_api_ptr->cancel_request_son_maintenance(name, true); @@ -756,7 +1653,9 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) // check SON is active son_obj = con.wallet_api_ptr->get_son(name); - BOOST_CHECK(son_obj.status == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::bitcoin) == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::hive) == son_status::active); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::ethereum) == son_status::active); // put SON in maintenance mode con.wallet_api_ptr->request_son_maintenance(name, true); @@ -764,15 +1663,18 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) // check SON is in request_maintenance son_obj = con.wallet_api_ptr->get_son(name); - BOOST_CHECK(son_obj.status == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::bitcoin) == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::hive) == son_status::request_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::ethereum) == son_status::request_maintenance); // process maintenance BOOST_CHECK(generate_maintenance_block()); // check SON is in maintenance son_obj = con.wallet_api_ptr->get_son(name); - BOOST_CHECK(son_obj.status == son_status::in_maintenance); - + BOOST_CHECK(son_obj.statuses.at(sidechain_type::bitcoin) == son_status::in_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::hive) == son_status::in_maintenance); + BOOST_CHECK(son_obj.statuses.at(sidechain_type::ethereum) == son_status::in_maintenance); } catch( fc::exception& e ) { BOOST_TEST_MESSAGE("SON cli wallet tests exception"); @@ -784,5 +1686,3 @@ BOOST_AUTO_TEST_CASE( maintenance_test ) BOOST_AUTO_TEST_SUITE_END() - - diff --git a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp index 82a121c9..c2b5908a 100644 --- a/tests/peerplays_sidechain/bitcoin_sign_tests.cpp +++ b/tests/peerplays_sidechain/bitcoin_sign_tests.cpp @@ -13,8 +13,8 @@ using namespace fc::ecc; BOOST_AUTO_TEST_SUITE(bitcoin_sign_tests) -const secp256k1_context_t *btc_context() { - static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); +const secp256k1_context *btc_context() { + static secp256k1_context *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); return ctx; } diff --git a/tests/peerplays_sidechain/ethereum_transaction_tests.cpp b/tests/peerplays_sidechain/ethereum_transaction_tests.cpp new file mode 100644 index 00000000..b3f094c8 --- /dev/null +++ b/tests/peerplays_sidechain/ethereum_transaction_tests.cpp @@ -0,0 +1,202 @@ +#include + +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain::ethereum; + +BOOST_AUTO_TEST_SUITE(ethereum_transaction_tests) + +BOOST_AUTO_TEST_CASE(withdrawal_encoder_test) { + const auto tx = withdrawal_encoder::encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.39.0"); + BOOST_CHECK_EQUAL(tx, "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"); + + const auto tx1 = withdrawal_encoder::encode("5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10000000000, "1.39.1"); + BOOST_CHECK_EQUAL(tx1, "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33392E310000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(withdrawal_signature_encoder_test) { + encoded_sign_transaction transaction; + transaction.data = "0xe088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"; + transaction.sign = sign_hash(keccak_hash(transaction.data), "0x21", "eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060" ); + + const auto function_signature = signature_encoder::get_function_signature_from_transaction(transaction.data); + BOOST_REQUIRE_EQUAL(function_signature.empty(), false); + const signature_encoder encoder{function_signature}; + const auto tx = encoder.encode({transaction}); + BOOST_CHECK_EQUAL(tx, "0xdaac6c810000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000006689c3a93d7059430d19ff952900dfada310c0dcced9ed046c335f886091c7e50c1a01016a488777b41a1815ca01a7d809ed47c36dcb0d5f86a43b079ce0d04afe00000000000000000000000000000000000000000000000000000000000000a4e088747b0000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc1200000000000000000000000000000000000000000000000000000002540be40000000000000000000000000000000000000000000000000000000000000000600000000000000000000000000000000000000000000000000000000000000006312E33392E30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(withdrawal_erc20_encoder_test) { + const auto tx = withdrawal_erc20_encoder::encode("cc806da9df9d634b5dac0aa36dca1e7780e42C60", "5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10, "1.39.0"); + BOOST_CHECK_EQUAL(tx, "0x483c0467000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42C600000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc12000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"); + + const auto tx1 = withdrawal_erc20_encoder::encode("cc806da9df9d634b5dac0aa36dca1e7780e42C60", "5fbbb31be52608d2f52247e8400b7fcaa9e0bc12", 10, "1.39.1"); + BOOST_CHECK_EQUAL(tx1, "0x483c0467000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42C600000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc12000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000006312E33392E310000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(withdrawal_erc20_signature_encoder_test) { + encoded_sign_transaction transaction; + transaction.data = "0x483c0467000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42C600000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc12000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"; + transaction.sign = sign_hash(keccak_hash(transaction.data), "0x21", "eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060" ); + + const auto function_signature = signature_encoder::get_function_signature_from_transaction(transaction.data); + BOOST_REQUIRE_EQUAL(function_signature.empty(), false); + const signature_encoder encoder{function_signature}; + const auto tx = encoder.encode({transaction}); + BOOST_CHECK_EQUAL(tx, "0xd2bf286600000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000066f51f7732435016936f0e21aa3c290023ea96ddbc369a957aca28a865cb5004a46675855fccd4bd5a283e1ff61aa60ca9b8b63664e770689e5cfc1a0c6bbdc79a00000000000000000000000000000000000000000000000000000000000000c4483c0467000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42C600000000000000000000000005fbbb31be52608d2f52247e8400b7fcaa9e0bc12000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000006312E33392E30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(update_owners_encoder_test) { + std::vector> owners_weights; + owners_weights.emplace_back("5FbBb31BE52608D2F52247E8400B7fCaA9E0bC12", 1); + owners_weights.emplace_back("76ce31bd03f601c3fc13732def921c5bac282676", 1); + + const auto tx = update_owners_encoder::encode(owners_weights, "1.39.0"); + BOOST_CHECK_EQUAL(tx, "0x23ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"); + + owners_weights.emplace_back("09ee460834498a4ee361beb819470061b7381b49", 1); + const auto tx1 = update_owners_encoder::encode(owners_weights, "1.39.1"); + BOOST_CHECK_EQUAL(tx1, "0x23ab6adf0000000000000000000000000000000000000000000000000000000000000040000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000000030000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac282676000000000000000000000000000000000000000000000000000000000000000100000000000000000000000009ee460834498a4ee361beb819470061b7381b4900000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33392E310000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(update_owners_signature_encoder_test) { + encoded_sign_transaction transaction; + transaction.data = "0x23ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33392E300000000000000000000000000000000000000000000000000000"; + transaction.sign = sign_hash(keccak_hash(transaction.data), "0x21", "eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060" ); + + const auto function_signature = signature_encoder::get_function_signature_from_transaction(transaction.data); + BOOST_REQUIRE_EQUAL(function_signature.empty(), false); + const signature_encoder encoder{function_signature}; + const auto tx = encoder.encode({transaction}); + BOOST_CHECK_EQUAL(tx, "0x9d608673000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000667121da3c6ab5054b1a77cac477f24e7ce7bfcb5e3a857cfcaf48e67fc8f003ac38dfa8821525383608a68c9f215f9a2a232e192ae80079cd2f31b0e01caa6e1d000000000000000000000000000000000000000000000000000000000000012423ab6adf000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000020000000000000000000000005FbBb31BE52608D2F52247E8400B7fCaA9E0bC12000000000000000000000000000000000000000000000000000000000000000100000000000000000000000076ce31bd03f601c3fc13732def921c5bac28267600000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000006312E33392E30000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"); +} + +BOOST_AUTO_TEST_CASE(deposit_erc20_decoder_test) { + const auto erc_20_1 = deposit_erc20_decoder::decode("0x97feb926000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42c600000000000000000000000000000000000000000000000000000000000000064"); + BOOST_REQUIRE_EQUAL(erc_20_1.valid(), true); + BOOST_CHECK_EQUAL(erc_20_1->token, "0xcc806da9df9d634b5dac0aa36dca1e7780e42c60"); + BOOST_CHECK_EQUAL(erc_20_1->amount, 100); + + const auto erc_20_2 = deposit_erc20_decoder::decode("0x97feb926000000000000000000000000cc806da9df9d634b5dac0aa36dca1e7780e42c600000000000000000000000000000000000000000000000006400000000000000"); + BOOST_REQUIRE_EQUAL(erc_20_2.valid(), true); + BOOST_CHECK_EQUAL(erc_20_2->token, "0xcc806da9df9d634b5dac0aa36dca1e7780e42c60"); + BOOST_CHECK_EQUAL(erc_20_2->amount, 7205759403792793600); +} + +BOOST_AUTO_TEST_CASE(raw_transaction_serialization_test) { + raw_transaction raw_tr; + raw_tr.nonce = "0x0"; + raw_tr.gas_price = "0x3b9aca07"; + raw_tr.gas_limit = "0x7a1200"; + raw_tr.to = "0x875a7e0eFe5140c80C5c822f99C02281C0290348"; + raw_tr.value = ""; + raw_tr.data = ""; + raw_tr.chain_id = "0x21"; + + const auto tx = raw_tr.serialize(); + BOOST_CHECK_EQUAL(tx, "0xE480843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C02903488080218080"); + + //! Change value + raw_tr.value = "0x1BC16D674EC80000"; + const auto tx1 = raw_tr.serialize(); + BOOST_CHECK_EQUAL(tx1, "0xEC80843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C0290348881BC16D674EC8000080218080"); + + //! Change data + raw_tr.data = "0x893d20e8"; + const auto tx2 = raw_tr.serialize(); + BOOST_CHECK_EQUAL(tx2, "0xF080843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C0290348881BC16D674EC8000084893D20E8218080"); +} + +BOOST_AUTO_TEST_CASE(raw_transaction_deserialization_test) { + const raw_transaction raw_tr{"E480843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C02903488080218080"}; + + BOOST_CHECK_EQUAL(raw_tr.nonce, "0x0"); + BOOST_CHECK_EQUAL(raw_tr.gas_price, "0x3b9aca07"); + BOOST_CHECK_EQUAL(raw_tr.gas_limit, "0x7a1200"); + BOOST_CHECK_EQUAL(raw_tr.to, "0x875a7e0efe5140c80c5c822f99c02281c0290348"); + BOOST_CHECK_EQUAL(raw_tr.value, "0x0"); + BOOST_CHECK_EQUAL(raw_tr.data, ""); + BOOST_CHECK_EQUAL(raw_tr.chain_id, "0x21"); +} + +BOOST_AUTO_TEST_CASE(raw_transaction_hash_test) { + raw_transaction raw_tr; + raw_tr.nonce = "0x0"; + raw_tr.gas_price = "0x3b9aca07"; + raw_tr.gas_limit = "0x7a1200"; + raw_tr.to = "0x875a7e0eFe5140c80C5c822f99C02281C0290348"; + raw_tr.value = ""; + raw_tr.data = ""; + raw_tr.chain_id = "0x21"; + + const auto tx = raw_tr.serialize(); + BOOST_CHECK_EQUAL(tx, "0xE480843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C02903488080218080"); + + const auto hash = raw_tr.hash(); + const auto hash_str = fc::to_hex((char *)&hash[0], hash.size()); + BOOST_CHECK_EQUAL(hash_str, "34934410cd305f4fa4e75a2c9294d625d6fbba729b5642ed2ca757ead50bb1fb"); +} + +BOOST_AUTO_TEST_CASE(sign_transaction_test) { + raw_transaction raw_tr; + raw_tr.nonce = "0x0"; + raw_tr.gas_price = "0x3b9aca07"; + raw_tr.gas_limit = "0x7a1200"; + raw_tr.to = "0x875a7e0eFe5140c80C5c822f99C02281C0290348"; + raw_tr.value = ""; + raw_tr.data = ""; + raw_tr.chain_id = "0x21"; + + const auto sign_tr = raw_tr.sign("eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060"); + BOOST_CHECK_EQUAL(sign_tr.r, "5f09de6ac850b2a9e94acd709c12d4e9adbabc6b72281ec0bbe13bca7e57c7ce"); + BOOST_CHECK_EQUAL(sign_tr.v, "65"); + BOOST_CHECK_EQUAL(sign_tr.s, "7ca5f26c5b3e25f14a32b18ac9a2a41b7c68efd3b04b118e1b1f4bf1c4e299b0"); +} + +BOOST_AUTO_TEST_CASE(sign_transaction_serialization_test) { + raw_transaction raw_tr; + raw_tr.nonce = "0x0"; + raw_tr.gas_price = "0x3b9aca07"; + raw_tr.gas_limit = "0x7a1200"; + raw_tr.to = "0x875a7e0eFe5140c80C5c822f99C02281C0290348"; + raw_tr.value = ""; + raw_tr.data = ""; + raw_tr.chain_id = "0x21"; + + const auto sign_tr = raw_tr.sign("eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060"); + const auto tx = sign_tr.serialize(); + BOOST_CHECK_EQUAL(tx, "0xF86480843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C0290348808065A05F09DE6AC850B2A9E94ACD709C12D4E9ADBABC6B72281EC0BBE13BCA7E57C7CEA07CA5F26C5B3E25F14A32B18AC9A2A41B7C68EFD3B04B118E1B1F4BF1C4E299B0"); +} + +BOOST_AUTO_TEST_CASE(sign_transaction_deserialization_test) { + const signed_transaction sign_tr{"0xF86480843B9ACA07837A120094875A7E0EFE5140C80C5C822F99C02281C0290348808065A05F09DE6AC850B2A9E94ACD709C12D4E9ADBABC6B72281EC0BBE13BCA7E57C7CEA07CA5F26C5B3E25F14A32B18AC9A2A41B7C68EFD3B04B118E1B1F4BF1C4E299B0"}; + + BOOST_CHECK_EQUAL(sign_tr.nonce, "0x0"); + BOOST_CHECK_EQUAL(sign_tr.gas_price, "0x3b9aca07"); + BOOST_CHECK_EQUAL(sign_tr.gas_limit, "0x7a1200"); + BOOST_CHECK_EQUAL(sign_tr.to, "0x875a7e0efe5140c80c5c822f99c02281c0290348"); + BOOST_CHECK_EQUAL(sign_tr.value, "0x0"); + BOOST_CHECK_EQUAL(sign_tr.data, ""); +} + +BOOST_AUTO_TEST_CASE(sign_transaction_recover_test) { + const std::string chain_id = "0x21"; + + raw_transaction raw_tr; + raw_tr.nonce = "0x0"; + raw_tr.gas_price = "0x3b9aca07"; + raw_tr.gas_limit = "0x7a1200"; + raw_tr.to = "0x875a7e0eFe5140c80C5c822f99C02281C0290348"; + raw_tr.value = ""; + raw_tr.data = ""; + raw_tr.chain_id = chain_id; + + const auto sign_tr = raw_tr.sign("eb5749a569e6141a3b08249d4a0d84f9ef22c67651ba29adb8eb6fd43fc83060"); + const auto from = sign_tr.recover(chain_id); + BOOST_CHECK_EQUAL(from, "0x5fbbb31be52608d2f52247e8400b7fcaa9e0bc12"); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/block_tests.cpp b/tests/tests/block_tests.cpp index a802aac5..c69768a3 100644 --- a/tests/tests/block_tests.cpp +++ b/tests/tests/block_tests.cpp @@ -1038,18 +1038,10 @@ BOOST_FIXTURE_TEST_CASE( hardfork_son2_time, database_fixture ) generate_block(); // get the maintenance skip slots out of the way*/ BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maximum_son_count(), 7); - generate_blocks(HARDFORK_SON3_TIME); + generate_blocks(HARDFORK_SON_FOR_ETHEREUM_TIME); // after this hardfork maximum son account should not reset the value - // on 7 after maintenance interval anymore. So change the global parameters - // and check the value after maintenance interval - db.modify(db.get_global_properties(), [](global_property_object& p) { - p.parameters.extensions.value.maximum_son_count = 13; - }); - - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - generate_block(); - - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maximum_son_count(), 13); + // on 7 after maintenance interval anymore. It must be HARDFORK_SON2_TIME + BOOST_CHECK_EQUAL(db.get_global_properties().parameters.maximum_son_count(), GRAPHENE_DEFAULT_MAX_SONS); } FC_LOG_AND_RETHROW() } diff --git a/tests/tests/confidential_tests.cpp b/tests/tests/confidential_tests.cpp deleted file mode 100644 index 792a300e..00000000 --- a/tests/tests/confidential_tests.cpp +++ /dev/null @@ -1,134 +0,0 @@ -/* - * Copyright (c) 2015 Cryptonomex, Inc., and contributors. - * - * The MIT License - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION 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 -#include -#include - -#include - -#include -#include "../common/database_fixture.hpp" - -using namespace graphene::chain; - -BOOST_FIXTURE_TEST_SUITE( confidential_tests, database_fixture ) -BOOST_AUTO_TEST_CASE( confidential_test ) -{ try { - ACTORS( (dan)(nathan) ) - const asset_object& core = asset_id_type()(db); - - transfer(account_id_type()(db), dan, core.amount(1000000)); - - transfer_to_blind_operation to_blind; - to_blind.amount = core.amount(1000); - to_blind.from = dan.id; - - auto owner1_key = fc::ecc::private_key::generate(); - auto owner1_pub = owner1_key.get_public_key(); - auto owner2_key = fc::ecc::private_key::generate(); - auto owner2_pub = owner2_key.get_public_key(); - - blind_output out1, out2; - out1.owner = authority( 1, public_key_type(owner1_pub), 1 ); - out2.owner = authority( 1, public_key_type(owner2_pub), 1 ); - - - auto InB1 = fc::sha256::hash("InB1"); - auto InB2 = fc::sha256::hash("InB2"); - auto nonce1 = fc::sha256::hash("nonce"); - auto nonce2 = fc::sha256::hash("nonce2"); - - out1.commitment = fc::ecc::blind(InB1,250); - out1.range_proof = fc::ecc::range_proof_sign( 0, out1.commitment, InB1, nonce1, 0, 0, 250 ); - - out2.commitment = fc::ecc::blind(InB2,750); - out2.range_proof = fc::ecc::range_proof_sign( 0, out2.commitment, InB1, nonce2, 0, 0, 750 ); - - to_blind.blinding_factor = fc::ecc::blind_sum( {InB1,InB2}, 2 ); - to_blind.outputs = {out2,out1}; - - trx.operations = {to_blind}; - sign( trx, dan_private_key ); - db.push_transaction(trx); - trx.clear_signatures(); - - BOOST_TEST_MESSAGE( "Transfering from blind to blind with change address" ); - auto Out3B = fc::sha256::hash("Out3B"); - auto Out4B = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b - blind_output out3, out4; - out3.commitment = fc::ecc::blind(Out3B,300); - out3.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 300 ); - out4.commitment = fc::ecc::blind(Out4B,750-300-10); - out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-10 ); - - - blind_transfer_operation blind_tr; - blind_tr.fee = core.amount(10); - blind_tr.inputs.push_back( {out2.commitment, out2.owner} ); - blind_tr.outputs = {out3,out4}; - blind_tr.validate(); - trx.operations = {blind_tr}; - sign( trx, owner2_key ); - db.push_transaction(trx); - - BOOST_TEST_MESSAGE( "Attempting to double spend the same commitments" ); - blind_tr.fee = core.amount(11); - - Out4B = fc::ecc::blind_sum( {InB2,Out3B}, 1 ); // add InB2 - Out3b - out4.commitment = fc::ecc::blind(Out4B,750-300-11); - auto out4_amount = 750-300-10; - out4.range_proof = fc::ecc::range_proof_sign( 0, out3.commitment, InB1, nonce1, 0, 0, 750-300-11 ); - blind_tr.outputs = {out4,out3}; - trx.operations = {blind_tr}; - BOOST_REQUIRE_THROW( db.push_transaction(trx, ~0), graphene::chain::blind_transfer_unknown_commitment ); - - - BOOST_TEST_MESSAGE( "Transfering from blind to nathan public" ); - out4.commitment = fc::ecc::blind(Out4B,750-300-10); - - transfer_from_blind_operation from_blind; - from_blind.fee = core.amount(10); - from_blind.to = nathan.id; - from_blind.amount = core.amount( out4_amount - 10 ); - from_blind.blinding_factor = Out4B; - from_blind.inputs.push_back( {out4.commitment, out4.owner} ); - trx.operations = {from_blind}; - trx.clear_signatures(); - db.push_transaction(trx); - - BOOST_REQUIRE_EQUAL( get_balance( nathan, core ), 750-300-10-10 ); - -} FC_LOG_AND_RETHROW() } - - - -BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/fee_tests.cpp b/tests/tests/fee_tests.cpp index 86dd3d7f..5faf4038 100644 --- a/tests/tests/fee_tests.cpp +++ b/tests/tests/fee_tests.cpp @@ -741,212 +741,6 @@ BOOST_AUTO_TEST_CASE( account_create_fee_scaling ) // FC_LOG_AND_RETHROW() // } -BOOST_AUTO_TEST_CASE( stealth_fba_test ) -{ - try - { - ACTORS( (alice)(bob)(chloe)(dan)(izzy)(philbin)(tom) ); - upgrade_to_lifetime_member(philbin_id); - - generate_blocks( HARDFORK_538_TIME ); - generate_blocks( HARDFORK_555_TIME ); - generate_blocks( HARDFORK_563_TIME ); - generate_blocks( HARDFORK_572_TIME ); - generate_blocks( HARDFORK_599_TIME ); - - // Philbin (registrar who registers Rex) - - // Izzy (initial issuer of stealth asset, will later transfer to Tom) - // Alice, Bob, Chloe, Dan (ABCD) - // Rex (recycler -- buyback account for stealth asset) - // Tom (owner of stealth asset who will be set as top_n authority) - - // Izzy creates STEALTH - asset_id_type stealth_id = create_user_issued_asset( "STEALTH", izzy_id(db), - disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee ).id; - - /* - // this is disabled because it doesn't work, our modify() is probably being overwritten by undo - - // - // Init blockchain with stealth ID's - // On a real chain, this would be done with #define GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET - // causing the designated_asset fields of these objects to be set at genesis, but for - // this test we modify the db directly. - // - auto set_fba_asset = [&]( uint64_t fba_acc_id, asset_id_type asset_id ) - { - db.modify( fba_accumulator_id_type(fba_acc_id)(db), [&]( fba_accumulator_object& fba ) - { - fba.designated_asset = asset_id; - } ); - }; - - set_fba_asset( fba_accumulator_id_transfer_to_blind , stealth_id ); - set_fba_asset( fba_accumulator_id_blind_transfer , stealth_id ); - set_fba_asset( fba_accumulator_id_transfer_from_blind, stealth_id ); - */ - - // Izzy kills some permission bits (this somehow happened to the real STEALTH in production) - { - asset_update_operation update_op; - update_op.issuer = izzy_id; - update_op.asset_to_update = stealth_id; - asset_options new_options; - new_options = stealth_id(db).options; - new_options.issuer_permissions = charge_market_fee; - new_options.flags = disable_confidential | transfer_restricted | override_authority | white_list | charge_market_fee; - // after fixing #579 you should be able to delete the following line - new_options.core_exchange_rate = price( asset( 1, stealth_id ), asset( 1, asset_id_type() ) ); - update_op.new_options = new_options; - signed_transaction tx; - tx.operations.push_back( update_op ); - set_expiration( db, tx ); - sign( tx, izzy_private_key ); - PUSH_TX( db, tx ); - } - - // Izzy transfers issuer duty to Tom - { - asset_update_operation update_op; - update_op.issuer = izzy_id; - update_op.asset_to_update = stealth_id; - update_op.new_issuer = tom_id; - // new_options should be optional, but isn't...the following line should be unnecessary #580 - update_op.new_options = stealth_id(db).options; - signed_transaction tx; - tx.operations.push_back( update_op ); - set_expiration( db, tx ); - sign( tx, izzy_private_key ); - PUSH_TX( db, tx ); - } - - // Tom re-enables the permission bits to clear the flags, then clears them again - // Allowed by #572 when current_supply == 0 - { - asset_update_operation update_op; - update_op.issuer = tom_id; - update_op.asset_to_update = stealth_id; - asset_options new_options; - new_options = stealth_id(db).options; - new_options.issuer_permissions = new_options.flags | charge_market_fee; - update_op.new_options = new_options; - signed_transaction tx; - // enable perms is one op - tx.operations.push_back( update_op ); - - new_options.issuer_permissions = charge_market_fee; - new_options.flags = charge_market_fee; - update_op.new_options = new_options; - // reset wrongly set flags and reset permissions can be done in a single op - tx.operations.push_back( update_op ); - - set_expiration( db, tx ); - sign( tx, tom_private_key ); - PUSH_TX( db, tx ); - } - - // Philbin registers Rex who will be the asset's buyback, including sig from the new issuer (Tom) - account_id_type rex_id; - { - buyback_account_options bbo; - bbo.asset_to_buy = stealth_id; - bbo.asset_to_buy_issuer = tom_id; - bbo.markets.emplace( asset_id_type() ); - account_create_operation create_op = make_account( "rex" ); - create_op.registrar = philbin_id; - create_op.extensions.value.buyback_options = bbo; - create_op.owner = authority::null_authority(); - create_op.active = authority::null_authority(); - - signed_transaction tx; - tx.operations.push_back( create_op ); - set_expiration( db, tx ); - sign( tx, philbin_private_key ); - sign( tx, tom_private_key ); - - processed_transaction ptx = PUSH_TX( db, tx ); - rex_id = ptx.operation_results.back().get< object_id_type >(); - } - - // Tom issues some asset to Alice and Bob - set_expiration( db, trx ); // #11 - issue_uia( alice_id, asset( 1000, stealth_id ) ); - issue_uia( bob_id, asset( 1000, stealth_id ) ); - - // Tom sets his authority to the top_n of the asset - { - top_holders_special_authority top2; - top2.num_top_holders = 2; - top2.asset = stealth_id; - - account_update_operation op; - op.account = tom_id; - op.extensions.value.active_special_authority = top2; - op.extensions.value.owner_special_authority = top2; - - signed_transaction tx; - tx.operations.push_back( op ); - - set_expiration( db, tx ); - sign( tx, tom_private_key ); - - PUSH_TX( db, tx ); - } - - // Wait until the next maintenance interval for top_n to take effect - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - - // Do a blind op to add some fees to the pool. - fund( chloe_id(db), asset( 100000, asset_id_type() ) ); - - auto create_transfer_to_blind = [&]( account_id_type account, asset amount, const std::string& key ) -> transfer_to_blind_operation - { - fc::ecc::private_key blind_key = fc::ecc::private_key::regenerate( fc::sha256::hash( key+"-privkey" ) ); - public_key_type blind_pub = blind_key.get_public_key(); - - fc::sha256 secret = fc::sha256::hash( key+"-secret" ); - fc::sha256 nonce = fc::sha256::hash( key+"-nonce" ); - - transfer_to_blind_operation op; - blind_output blind_out; - blind_out.owner = authority( 1, blind_pub, 1 ); - blind_out.commitment = fc::ecc::blind( secret, amount.amount.value ); - blind_out.range_proof = fc::ecc::range_proof_sign( 0, blind_out.commitment, secret, nonce, 0, 0, amount.amount.value ); - - op.amount = amount; - op.from = account; - op.blinding_factor = fc::ecc::blind_sum( {secret}, 1 ); - op.outputs = {blind_out}; - - return op; - }; - - { - transfer_to_blind_operation op = create_transfer_to_blind( chloe_id, asset( 5000, asset_id_type() ), "chloe-key" ); - op.fee = asset( 1000, asset_id_type() ); - - signed_transaction tx; - tx.operations.push_back( op ); - set_expiration( db, tx ); - sign( tx, chloe_private_key ); - - PUSH_TX( db, tx ); - } - - // wait until next maint interval - generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); - - idump( ( get_operation_history( chloe_id ) ) ); - idump( ( get_operation_history( rex_id ) ) ); - idump( ( get_operation_history( tom_id ) ) ); - } - catch( const fc::exception& e ) - { - elog( "caught exception ${e}", ("e", e.to_detail_string()) ); - throw; - } -} // added test from bitshares for issues: // https://github.com/bitshares/bitshares-core/issues/429 // https://github.com/bitshares/bitshares-core/issues/433 diff --git a/tests/tests/nft_lottery_tests.cpp b/tests/tests/nft_lottery_tests.cpp index 02d8bdc1..e0ecf5bb 100644 --- a/tests/tests/nft_lottery_tests.cpp +++ b/tests/tests/nft_lottery_tests.cpp @@ -193,6 +193,34 @@ BOOST_AUTO_TEST_CASE(tickets_purchase_fail_test) } } +BOOST_AUTO_TEST_CASE(tickets_purchase_overflow) +{ + try + { + nft_metadata_id_type test_nft_md_id = db.get_index().get_next_id(); + INVOKE(create_lottery_nft_md_test); + auto &test_nft_md_obj = test_nft_md_id(db); + + nft_lottery_token_purchase_operation tpo; + tpo.fee = asset(); + tpo.buyer = account_id_type(); + tpo.lottery_id = test_nft_md_obj.id; + tpo.tickets_to_buy = 9223372036854775800; // Large number so that the overall amount overflows + trx.operations.push_back(tpo); + BOOST_REQUIRE_THROW(PUSH_TX(db, trx, ~0), fc::overflow_exception); + trx.operations.clear(); + + tpo.tickets_to_buy = -2; // Negative value should also be rejected + trx.operations.push_back(tpo); + BOOST_REQUIRE_THROW(PUSH_TX(db, trx, ~0), fc::exception); + } + catch (fc::exception &e) + { + edump((e.to_detail_string())); + throw; + } +} + BOOST_AUTO_TEST_CASE(lottery_end_by_stage_test) { try diff --git a/tests/tests/nft_tests.cpp b/tests/tests/nft_tests.cpp index b8fd19ea..83643eed 100644 --- a/tests/tests/nft_tests.cpp +++ b/tests/tests/nft_tests.cpp @@ -4,76 +4,204 @@ #include #include +#include + using namespace graphene::chain; using namespace graphene::chain::test; + +class nft_test_helper +{ + database_fixture& fixture_; + +public: + nft_test_helper(database_fixture& fixture): + fixture_(fixture) + { + fixture_.generate_blocks(HARDFORK_NFT_TIME); + fixture_.generate_block(); + fixture_.generate_block(); + set_expiration(fixture_.db, fixture_.trx); + } + + nft_metadata_object create_metadata(const std::string& name, const std::string& symbol, const std::string& uri, const account_id_type& owner, const fc::ecc::private_key &priv_key) + { + const auto& idx_by_id = fixture_.db.get_index_type().indices().get(); + size_t obj_count0 = idx_by_id.size(); + + fixture_.generate_block(); + + signed_transaction trx; + set_expiration(fixture_.db, trx); + + nft_metadata_create_operation op; + op.owner = owner; + op.symbol = symbol; + op.base_uri = uri; + op.name = name; + op.is_transferable = true; + BOOST_CHECK_NO_THROW(op.validate()); + trx.operations.push_back(op); + fixture_.sign(trx, priv_key); + PUSH_TX(fixture_.db, trx, ~0); + fixture_.generate_block(); + + BOOST_REQUIRE( idx_by_id.size() == obj_count0 + 1 ); // one more metadata created + + const auto& idx_by_name = fixture_.db.get_index_type().indices().get(); + auto obj = idx_by_name.find(name); + BOOST_CHECK( obj->owner == owner ); + BOOST_CHECK( obj->name == name ); + BOOST_CHECK( obj->symbol == symbol ); + BOOST_CHECK( obj->base_uri == uri ); + return *obj; + } + + + nft_object mint(const nft_metadata_id_type& metadata, const account_id_type& owner, const account_id_type& payer, + const fc::optional& approved, const std::vector& approved_operators, + const fc::ecc::private_key &priv_key) + { + const auto& idx_by_id = fixture_.db.get_index_type().indices().get(); + size_t obj_count0 = idx_by_id.size(); + + fixture_.generate_block(); + + signed_transaction trx; + set_expiration(fixture_.db, trx); + + nft_mint_operation op; + op.nft_metadata_id = metadata; + op.payer = payer; + op.owner = owner; + if (approved) + op.approved = *approved; + op.approved_operators = approved_operators; + + trx.operations.push_back(op); + fixture_.sign(trx, priv_key); + PUSH_TX(fixture_.db, trx, ~0); + + fixture_.generate_block(); + + BOOST_REQUIRE(idx_by_id.size() == obj_count0 + 1); // one more created + + auto obj = idx_by_id.rbegin(); + + BOOST_REQUIRE(obj != idx_by_id.rend()); + BOOST_CHECK(obj->owner == owner); + BOOST_CHECK(obj->approved_operators.size() == approved_operators.size()); + BOOST_CHECK(obj->approved_operators == approved_operators); + + return *obj; + } +}; + + BOOST_FIXTURE_TEST_SUITE( nft_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( nft_metadata_name_validation_test ) { + BOOST_TEST_MESSAGE("nft_metadata_name_validation_test"); + ACTORS((mdowner)); + nft_metadata_create_operation op; + op.owner = mdowner_id; + op.symbol = "NFT"; + op.base_uri = "http://nft.example.com"; + op.name = "123"; + op.is_transferable = true; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ""; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "1ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = ".abc"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "abc."; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ABC"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abcdefghijklmnopq"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "ab"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "***"; + BOOST_CHECK_THROW(op.validate(), fc::exception); + op.name = "a12"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "a1b"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "abc123defg12345"; + BOOST_CHECK_NO_THROW(op.validate()); + op.name = "NFT Test"; + BOOST_CHECK_NO_THROW(op.validate()); +} + + BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) { BOOST_TEST_MESSAGE("nft_metadata_create_test"); - generate_blocks(HARDFORK_NFT_TIME); - generate_block(); - generate_block(); - set_expiration(db, trx); - + nft_test_helper nfth(*this); ACTORS((mdowner)); + nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key); +} - generate_block(); - set_expiration(db, trx); +BOOST_AUTO_TEST_CASE( nft_metadata_listing_test ) { + + BOOST_TEST_MESSAGE("nft_metadata_listing_test"); + + nft_test_helper nfth(*this); + + ACTORS((mdowner1)); + ACTORS((mdowner2)); + + // prepare metadata set + for (int i=0; i < 200; i++) { - BOOST_TEST_MESSAGE("Send nft_metadata_create_operation"); - - nft_metadata_create_operation op; - op.owner = mdowner_id; - op.symbol = "NFT"; - op.base_uri = "http://nft.example.com"; - op.name = "123"; - op.is_transferable = true; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = ""; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "1ab"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = ".abc"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "abc."; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "ABC"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abcdefghijklmnopq"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "ab"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "***"; - BOOST_CHECK_THROW(op.validate(), fc::exception); - op.name = "a12"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "a1b"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abc"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "abc123defg12345"; - BOOST_CHECK_NO_THROW(op.validate()); - op.name = "NFT Test"; - trx.operations.push_back(op); - sign(trx, mdowner_private_key); - PUSH_TX(db, trx, ~0); + string sfx = fc::to_pretty_string(i); + nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner1_id, mdowner1_private_key); + BOOST_REQUIRE(md.id == nft_metadata_id_type(i)); + } + for (int i=200; i < 250; i++) + { + string sfx = fc::to_pretty_string(i); + nft_metadata_object md = nfth.create_metadata("NFT Test " + sfx, "NFT" + sfx, "http://nft.example.com", mdowner2_id, mdowner2_private_key); + BOOST_REQUIRE(md.id == nft_metadata_id_type(i)); } - generate_block(); - BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results"); + graphene::app::database_api db_api(db); + vector listed; - const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 1 ); - auto obj = idx.begin(); - BOOST_REQUIRE( obj != idx.end() ); - BOOST_CHECK( obj->owner == mdowner_id ); - BOOST_CHECK( obj->name == "NFT Test" ); - BOOST_CHECK( obj->symbol == "NFT" ); - BOOST_CHECK( obj->base_uri == "http://nft.example.com" ); + // first 100 returned + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(99)); + + // 100 starting from 50 + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(50), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type( 50)); + BOOST_REQUIRE(listed[99].id == nft_metadata_id_type(149)); + + // the last 5 must be returned + listed = db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(listed[0].id == nft_metadata_id_type(195)); + BOOST_REQUIRE(listed[4].id == nft_metadata_id_type(199)); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_metadata_by_owner(mdowner1_id, nft_metadata_id_type(0), 101), fc::exception); + + // the last 40 must be returned + listed = db_api.nft_get_metadata_by_owner(mdowner2_id, nft_metadata_id_type(210), 100); + BOOST_REQUIRE(listed.size() == 40); + BOOST_REQUIRE(listed[ 0].id == nft_metadata_id_type(210)); + BOOST_REQUIRE(listed[39].id == nft_metadata_id_type(249)); } @@ -120,49 +248,112 @@ BOOST_AUTO_TEST_CASE( nft_mint_test ) { generate_block(); set_expiration(db, trx); - INVOKE(nft_metadata_create_test); + nft_test_helper nfth(*this); + ACTORS((mdowner)); ACTORS((alice)); ACTORS((bob)); ACTORS((operator1)); ACTORS((operator2)); - GET_ACTOR(mdowner); + nft_metadata_object md = nfth.create_metadata("NFT Test", "NFT", "http://nft.example.com", mdowner_id, mdowner_private_key); - generate_block(); - set_expiration(db, trx); + nfth.mint(md.id, alice_id, mdowner_id, alice_id, {operator1_id, operator2_id}, alice_private_key); +} + +BOOST_AUTO_TEST_CASE( nft_object_listing_test ) { + + BOOST_TEST_MESSAGE("nft_object_listing_test"); + + nft_test_helper nfth(*this); + + ACTORS((mdowner1)); + ACTORS((mdowner2)); + ACTORS((alice)); + ACTORS((bob)); + + nft_metadata_object md1 = nfth.create_metadata("NFT Test 1", "NFT1", "http://nft.example.com", mdowner1_id, mdowner1_private_key); + nft_metadata_object md2 = nfth.create_metadata("NFT Test 2", "NFT2", "http://nft.example.com", mdowner2_id, mdowner2_private_key); + + // create NFT objects: 200 owned by alice and 200 by bob + for (int i=0; i < 200; i++) { - BOOST_TEST_MESSAGE("Send nft_mint_operation"); - - const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 1 ); - auto nft_md_obj = idx.begin(); - - nft_mint_operation op; - op.payer = mdowner_id; - op.nft_metadata_id = nft_md_obj->id; - op.owner = alice_id; - op.approved = alice_id; - op.approved_operators.push_back(operator1_id); - op.approved_operators.push_back(operator2_id); - - trx.operations.push_back(op); - sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + nft_object nft = nfth.mint(md1.id, alice_id, mdowner1_id, alice_id, {}, alice_private_key); + BOOST_REQUIRE(nft.id == nft_id_type(i)); + } + for (int i=200; i < 250; i++) + { + nft_object nft = nfth.mint(md1.id, bob_id, mdowner1_id, bob_id, {}, bob_private_key); + BOOST_REQUIRE(nft.id == nft_id_type(i)); } - generate_block(); - BOOST_TEST_MESSAGE("Check nft_mint_operation results"); + graphene::app::database_api db_api(db); + vector listed; - const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 1 ); - auto obj = idx.begin(); - BOOST_REQUIRE( obj != idx.end() ); - BOOST_CHECK( obj->owner == alice_id ); - BOOST_CHECK( obj->approved_operators.size() == 2 ); - BOOST_CHECK( obj->approved_operators.at(0) == operator1_id ); - BOOST_CHECK( obj->approved_operators.at(1) == operator2_id ); + // + // listing all tokens: + // + // first 100 returned, all alice's + listed = db_api.nft_get_all_tokens(nft_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_id_type(99)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + + // 100 starting from 50, all alice's + listed = db_api.nft_get_all_tokens(nft_id_type(50), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 50)); + BOOST_REQUIRE(listed[99].id == nft_id_type(149)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + + // the last 5 must be returned, all bob's + listed = db_api.nft_get_all_tokens(nft_id_type(245), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(listed[0].id == nft_id_type(245)); + BOOST_REQUIRE(listed[4].id == nft_id_type(249)); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; })); + + // 10 from the middle of the set, half alice's, half bob's + listed = db_api.nft_get_all_tokens(nft_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 10); + BOOST_REQUIRE(listed[0].id == nft_id_type(195)); + BOOST_REQUIRE(listed[9].id == nft_id_type(204)); + BOOST_REQUIRE(listed[0].owner == alice_id); + BOOST_REQUIRE(listed[4].owner == alice_id); + BOOST_REQUIRE(listed[5].owner == bob_id); + BOOST_REQUIRE(listed[9].owner == bob_id); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_all_tokens(nft_id_type(0), 101), fc::exception); + + // + // listing tokens by owner: + // + // first 100 alice's + listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 100); + BOOST_REQUIRE(listed.size() == 100); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + BOOST_REQUIRE(listed[ 0].id == nft_id_type( 0)); + BOOST_REQUIRE(listed[99].id == nft_id_type(99)); + + // the last 5 alice's must be returned + listed = db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(195), 10); + BOOST_REQUIRE(listed.size() == 5); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [alice_id](const nft_object &obj){ return obj.owner == alice_id; })); + BOOST_REQUIRE(listed[0].id == nft_id_type(195)); + BOOST_REQUIRE(listed[4].id == nft_id_type(199)); + + // all 50 bob's + listed = db_api.nft_get_tokens_by_owner(bob_id, nft_id_type(0), 60); + BOOST_REQUIRE(listed.size() == 50); + BOOST_REQUIRE(all_of(listed.begin(), listed.end(), [bob_id](const nft_object &obj){ return obj.owner == bob_id; })); + BOOST_REQUIRE(listed[ 0].id == nft_id_type(200)); + BOOST_REQUIRE(listed[49].id == nft_id_type(249)); + + // too much requested at once + BOOST_CHECK_THROW(db_api.nft_get_tokens_by_owner(alice_id, nft_id_type(0), 101), fc::exception); } diff --git a/tests/tests/sidechain_addresses_test.cpp b/tests/tests/sidechain_addresses_test.cpp index cd0fb876..8c3229d3 100644 --- a/tests/tests/sidechain_addresses_test.cpp +++ b/tests/tests/sidechain_addresses_test.cpp @@ -6,6 +6,7 @@ #include #include #include +#include using namespace graphene::chain; using namespace graphene::chain::test; @@ -63,9 +64,135 @@ BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { INVOKE(sidechain_address_add_test); - GET_ACTOR(alice); + generate_block(); + + //! ----- BEGIN CREATE SON bob ----- + ACTORS((bob)); + upgrade_to_lifetime_member(bob); + + transfer( committee_account, bob_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + + set_expiration(db, trx); + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(10*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.clear(); + trx.operations.push_back(op); + + // amount in the son balance need to be at least 50 + GRAPHENE_REQUIRE_THROW( PUSH_TX( db, trx ), fc::exception ); + + op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); + trx.clear(); + + trx.operations.push_back(op); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + deposit = ptx.operation_results[0].get(); + + auto deposit_vesting = db.get(ptx.operation_results[0].get()); + + BOOST_CHECK_EQUAL(deposit(db).balance.amount.value, 50*GRAPHENE_BLOCKCHAIN_PRECISION); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit(db).is_withdraw_allowed(now, asset(50*GRAPHENE_BLOCKCHAIN_PRECISION)), false); // cant withdraw + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment ; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + op.policy = linear_vesting_policy_initializer {}; + op.validate(); + + trx.clear(); + trx.operations.push_back(op); + trx.validate(); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment = ptx.operation_results[0].get(); + } + + generate_block(); + set_expiration(db, trx); + + // bob became son + { + flat_map sidechain_public_keys; + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + sidechain_public_keys[sidechain_type::hive] = "hive address"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address"; + + son_create_operation op; + op.owner_account = bob_id; + op.url = test_url; + op.deposit = deposit; + op.pay_vb = payment; + op.signing_key = bob_public_key; + op.sidechain_public_keys = sidechain_public_keys; + + trx.clear(); + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + + { + const auto &idx = db.get_index_type().indices().get(); + BOOST_REQUIRE(idx.size() == 1); + auto obj = idx.find(bob_id); + BOOST_REQUIRE(obj != idx.end()); + BOOST_CHECK(obj->url == test_url); + BOOST_CHECK(obj->signing_key == bob_public_key); + BOOST_CHECK(obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "bitcoin address"); + BOOST_CHECK(obj->deposit.instance == deposit.instance.value); + BOOST_CHECK(obj->pay_vb.instance == payment.instance.value); + } + + // Note payment time just to generate enough blocks to make budget + const auto block_interval = db.get_global_properties().parameters.block_interval; + auto pay_fee_time = db.head_block_time().sec_since_epoch(); + generate_block(); + // Do maintenance from the upcoming block + auto schedule_maint = [&]() + { + db.modify( db.get_dynamic_global_properties(), [&]( dynamic_global_property_object& _dpo ) + { + _dpo.next_maintenance_time = db.head_block_time() + 1; + } ); + }; + + // Generate enough blocks to make budget + while( db.head_block_time().sec_since_epoch() - pay_fee_time < 100 * block_interval ) + { + generate_block(); + } + + // Enough blocks generated schedule maintenance now + schedule_maint(); + // This block triggers maintenance + generate_block(); + + //! ----- END CREATE SON bob ----- + + GET_ACTOR(alice); + const auto& idx = db.get_index_type().indices().get(); BOOST_REQUIRE( idx.size() == 1 ); auto obj = idx.find( boost::make_tuple( alice_id, sidechain_type::bitcoin, time_point_sec::maximum() ) ); @@ -77,19 +204,7 @@ BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { std::string new_withdraw_address = "withdraw_address"; generate_block(); - auto& son = db.create( [&]( son_object& sobj ) - { - sobj.son_account = bob_id; - sobj.statistics = db.create([&](son_statistics_object& s){s.owner = sobj.id;}).id; - }); - generate_block(); - db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) - { - son_info sinfo; - sinfo.son_id = son.id; - _gpo.active_sons.push_back(sinfo); - }); - generate_block(); + set_expiration(db, trx); { BOOST_TEST_MESSAGE("Send sidechain_address_update_operation"); trx.clear(); @@ -154,9 +269,8 @@ BOOST_AUTO_TEST_CASE( sidechain_address_delete_test ) { sign(trx, alice_private_key); PUSH_TX(db, trx, ~0); } - //time_point_sec now = db.head_block_time(); - generate_block(); + generate_block(); { BOOST_TEST_MESSAGE("Check sidechain_address_delete_operation results"); diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index 1e3bb7e4..0d671a62 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -15,7 +15,7 @@ using namespace graphene::chain::test; BOOST_FIXTURE_TEST_SUITE( son_operation_tests, database_fixture ) BOOST_AUTO_TEST_CASE( create_son_test ) { - generate_blocks(HARDFORK_SON_TIME); + generate_blocks(HARDFORK_SON_FOR_ETHEREUM_TIME); generate_block(); set_expiration(db, trx); @@ -87,6 +87,8 @@ BOOST_AUTO_TEST_CASE( create_son_test ) { { flat_map sidechain_public_keys; sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + sidechain_public_keys[sidechain_type::hive] = "hive account"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address"; son_create_operation op; op.owner_account = alice_id; @@ -111,6 +113,8 @@ BOOST_AUTO_TEST_CASE( create_son_test ) { BOOST_CHECK( obj->url == test_url ); BOOST_CHECK( obj->signing_key == alice_public_key ); BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "bitcoin address" ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::hive) == "hive account" ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::ethereum) == "ethereum address" ); BOOST_CHECK( obj->deposit.instance == deposit.instance.value ); BOOST_CHECK( obj->pay_vb.instance == payment.instance.value ); } @@ -124,7 +128,9 @@ BOOST_AUTO_TEST_CASE( update_son_test ) { { flat_map sidechain_public_keys; - sidechain_public_keys[sidechain_type::bitcoin] = "new bitcoin address"; + sidechain_public_keys[sidechain_type::bitcoin] = "bitcoin address"; + sidechain_public_keys[sidechain_type::hive] = "hive account"; + sidechain_public_keys[sidechain_type::ethereum] = "ethereum address"; son_update_operation op; op.son_id = son_id_type(0); @@ -143,7 +149,9 @@ BOOST_AUTO_TEST_CASE( update_son_test ) { auto obj = idx.find( alice_id ); BOOST_REQUIRE( obj != idx.end() ); BOOST_CHECK( obj->url == new_url ); - BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "new bitcoin address" ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::bitcoin) == "bitcoin address" ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::hive) == "hive account" ); + BOOST_CHECK( obj->sidechain_public_keys.at(sidechain_type::ethereum) == "ethereum address" ); } BOOST_AUTO_TEST_CASE( deregister_son_test ) { @@ -193,12 +201,16 @@ try { // Modify SON's status to active db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::in_maintenance; + _s.statuses[sidechain_type::bitcoin] = son_status::in_maintenance; + _s.statuses[sidechain_type::hive] = son_status::in_maintenance; + _s.statuses[sidechain_type::ethereum] = son_status::in_maintenance; }); db.modify( *son_stats_obj, [&]( son_statistics_object& _s) { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + _s.last_active_timestamp[sidechain_type::bitcoin] = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + _s.last_active_timestamp[sidechain_type::hive] = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + _s.last_active_timestamp[sidechain_type::ethereum] = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); }); auto deposit_vesting = db.get(vesting_balance_id_type(0)); @@ -218,7 +230,9 @@ try { generate_block(); BOOST_REQUIRE( idx.size() == 1 ); - BOOST_REQUIRE( obj->status == son_status::deregistered ); + BOOST_REQUIRE( obj->statuses.at(sidechain_type::bitcoin) == son_status::deregistered ); + BOOST_REQUIRE( obj->statuses.at(sidechain_type::hive) == son_status::deregistered ); + BOOST_REQUIRE( obj->statuses.at(sidechain_type::ethereum) == son_status::deregistered ); BOOST_REQUIRE( son_stats_obj->deregistered_timestamp == now ); deposit_vesting = db.get(vesting_balance_id_type(0)); @@ -300,7 +314,7 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) const dynamic_global_property_object& dpo = db.get_dynamic_global_properties(); const auto block_interval = db.get_global_properties().parameters.block_interval; BOOST_CHECK( dpo.son_budget.value == 0); - generate_blocks(HARDFORK_SON_TIME); + generate_blocks(HARDFORK_SON_FOR_ETHEREUM_TIME); while (db.head_block_time() <= HARDFORK_SON_TIME) { generate_block(); } @@ -493,30 +507,38 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) { _s.txs_signed[sidechain_type::bitcoin] = 2; _s.txs_signed[sidechain_type::hive] = 4; + _s.txs_signed[sidechain_type::ethereum] = 6; _s.total_txs_signed[sidechain_type::bitcoin] = 2; _s.total_txs_signed[sidechain_type::hive] = 4; + _s.total_txs_signed[sidechain_type::ethereum] = 6; _s.sidechain_txs_reported[sidechain_type::bitcoin] = 4; _s.sidechain_txs_reported[sidechain_type::hive] = 8; + _s.sidechain_txs_reported[sidechain_type::ethereum] = 12; _s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 4; _s.total_sidechain_txs_reported[sidechain_type::hive] = 8; + _s.total_sidechain_txs_reported[sidechain_type::ethereum] = 12; }); // Modify the transaction signed statistics of Bob's SON db.modify( *son_stats_obj2, [&]( son_statistics_object& _s) { _s.txs_signed[sidechain_type::bitcoin] = 3; _s.txs_signed[sidechain_type::hive] = 6; + _s.txs_signed[sidechain_type::ethereum] = 9; _s.total_txs_signed[sidechain_type::bitcoin] = 3; _s.total_txs_signed[sidechain_type::hive] = 6; + _s.total_txs_signed[sidechain_type::ethereum] = 9; _s.sidechain_txs_reported[sidechain_type::bitcoin] = 6; _s.sidechain_txs_reported[sidechain_type::hive] = 12; + _s.sidechain_txs_reported[sidechain_type::ethereum] = 18; _s.total_sidechain_txs_reported[sidechain_type::bitcoin] = 6; _s.total_sidechain_txs_reported[sidechain_type::hive] = 12; + _s.total_sidechain_txs_reported[sidechain_type::ethereum] = 18; }); // Note the balances before the maintenance @@ -528,23 +550,31 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) // Check if the signed transaction statistics are reset for both SONs BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed.at(sidechain_type::bitcoin), 0); BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed.at(sidechain_type::hive), 0); + BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed.at(sidechain_type::ethereum), 0); BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed.at(sidechain_type::bitcoin), 0); BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed.at(sidechain_type::hive), 0); + BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed.at(sidechain_type::ethereum), 0); BOOST_REQUIRE_EQUAL(son_stats_obj1->sidechain_txs_reported.at(sidechain_type::bitcoin), 0); BOOST_REQUIRE_EQUAL(son_stats_obj1->sidechain_txs_reported.at(sidechain_type::hive), 0); + BOOST_REQUIRE_EQUAL(son_stats_obj1->sidechain_txs_reported.at(sidechain_type::ethereum), 0); BOOST_REQUIRE_EQUAL(son_stats_obj2->sidechain_txs_reported.at(sidechain_type::bitcoin), 0); BOOST_REQUIRE_EQUAL(son_stats_obj2->sidechain_txs_reported.at(sidechain_type::hive), 0); + BOOST_REQUIRE_EQUAL(son_stats_obj2->sidechain_txs_reported.at(sidechain_type::ethereum), 0); BOOST_REQUIRE_EQUAL(son_stats_obj1->total_txs_signed.at(sidechain_type::bitcoin), 2); BOOST_REQUIRE_EQUAL(son_stats_obj1->total_txs_signed.at(sidechain_type::hive), 4); + BOOST_REQUIRE_EQUAL(son_stats_obj1->total_txs_signed.at(sidechain_type::ethereum), 6); BOOST_REQUIRE_EQUAL(son_stats_obj2->total_txs_signed.at(sidechain_type::bitcoin), 3); BOOST_REQUIRE_EQUAL(son_stats_obj2->total_txs_signed.at(sidechain_type::hive), 6); + BOOST_REQUIRE_EQUAL(son_stats_obj2->total_txs_signed.at(sidechain_type::ethereum), 9); BOOST_REQUIRE_EQUAL(son_stats_obj1->total_sidechain_txs_reported.at(sidechain_type::bitcoin), 4); BOOST_REQUIRE_EQUAL(son_stats_obj1->total_sidechain_txs_reported.at(sidechain_type::hive), 8); + BOOST_REQUIRE_EQUAL(son_stats_obj1->total_sidechain_txs_reported.at(sidechain_type::ethereum), 12); BOOST_REQUIRE_EQUAL(son_stats_obj2->total_sidechain_txs_reported.at(sidechain_type::bitcoin), 6); BOOST_REQUIRE_EQUAL(son_stats_obj2->total_sidechain_txs_reported.at(sidechain_type::hive), 12); + BOOST_REQUIRE_EQUAL(son_stats_obj2->total_sidechain_txs_reported.at(sidechain_type::ethereum), 18); // Check that Alice and Bob are paid for signing the transactions in the previous day/cycle - BOOST_REQUIRE_EQUAL(db.get_balance(obj1->son_account, asset_id_type()).amount.value, 80+obj1_balance); + BOOST_REQUIRE_EQUAL(db.get_balance(obj1->son_account, asset_id_type()).amount.value, 79+obj1_balance); BOOST_REQUIRE_EQUAL(db.get_balance(obj2->son_account, asset_id_type()).amount.value, 120+obj2_balance); // Check the SON Budget is again allocated after maintenance BOOST_CHECK( dpo.son_budget.value == 200); @@ -604,12 +634,18 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { // Modify SON's status to active db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::active; + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + _s.statuses[active_sidechain_type] = son_status::active; + } }); db.modify( *son_stats_obj, [&]( son_statistics_object& _s) { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time()); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + _s.last_down_timestamp[active_sidechain_type] = fc::time_point_sec(db.head_block_time()); + } }); { @@ -626,7 +662,10 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { PUSH_TX( db, trx, ~0); generate_block(); trx.clear(); - BOOST_CHECK( obj->status == son_status::request_maintenance); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::request_maintenance); + } } { @@ -643,16 +682,26 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { PUSH_TX( db, trx, ~0); generate_block(); trx.clear(); - BOOST_CHECK( obj->status == son_status::active); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::active); + } } // Modify SON's status to in_maintenance db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::in_maintenance; + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + _s.statuses[active_sidechain_type] = son_status::in_maintenance; + } }); - uint64_t downtime = 0; + flat_map downtime; + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + downtime[active_sidechain_type] = 0; + } { generate_block(); @@ -668,24 +717,34 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { PUSH_TX( db, trx, ~0); generate_block(); trx.clear(); - BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); - downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); - BOOST_CHECK( obj->status == son_status::inactive); - BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime.at(active_sidechain_type), op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.at(active_sidechain_type).sec_since_epoch()); + downtime[active_sidechain_type] += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.at(active_sidechain_type).sec_since_epoch(); + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::inactive); + BOOST_CHECK( son_stats_obj->last_active_timestamp.at(active_sidechain_type) == op.ts); + } } // Modify SON's status to in_maintenance db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::in_maintenance; + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + _s.statuses[active_sidechain_type] = son_status::in_maintenance; + } }); // SON is selected as one of the active SONs db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) { - son_info son_inf; + son_sidechain_info son_inf; son_inf.son_id = son_id_type(0); - _gpo.active_sons.push_back(son_inf); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + _gpo.active_sons[active_sidechain_type].push_back(son_inf); + } }); { @@ -702,10 +761,14 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { PUSH_TX( db, trx, ~0); generate_block(); trx.clear(); - BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime + op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); - downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); - BOOST_CHECK( obj->status == son_status::active); - BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime.at(active_sidechain_type), downtime.at(active_sidechain_type) + op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.at(active_sidechain_type).sec_since_epoch()); + downtime[active_sidechain_type] += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.at(active_sidechain_type).sec_since_epoch(); + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp.at(active_sidechain_type) == op.ts); + } } { @@ -722,9 +785,13 @@ BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { PUSH_TX( db, trx, ~0); generate_block(); trx.clear(); - BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime); - BOOST_CHECK( obj->status == son_status::active); - BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime.at(active_sidechain_type), downtime.at(active_sidechain_type)); + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp.at(active_sidechain_type) == op.ts); + } } } FC_LOG_AND_RETHROW() } @@ -749,7 +816,10 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { auto son_stats_obj = sidx.find( obj->statistics ); BOOST_REQUIRE( son_stats_obj != sidx.end() ); - BOOST_CHECK( obj->status == son_status::active); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::active); + } { // Check that transaction fails if down_ts < last_active_timestamp @@ -758,7 +828,7 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { son_report_down_operation op; op.payer = db.get_global_properties().parameters.son_account(); op.son_id = son_id_type(0); - op.down_ts = fc::time_point_sec(son_stats_obj->last_active_timestamp - fc::seconds(1)); + op.down_ts = fc::time_point_sec(son_stats_obj->last_active_timestamp.at(sidechain_type::bitcoin) - fc::seconds(1)); trx.operations.push_back(op); set_expiration(db, trx); @@ -775,7 +845,7 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { son_report_down_operation op; op.payer = alice_id; op.son_id = son_id_type(0); - op.down_ts = son_stats_obj->last_active_timestamp; + op.down_ts = son_stats_obj->last_active_timestamp.at(sidechain_type::bitcoin); trx.operations.push_back(op); set_expiration(db, trx); @@ -792,7 +862,7 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { son_report_down_operation op; op.payer = db.get_global_properties().parameters.son_account(); op.son_id = son_id_type(0); - op.down_ts = son_stats_obj->last_active_timestamp; + op.down_ts = son_stats_obj->last_active_timestamp.at(sidechain_type::bitcoin); trx.operations.push_back(op); set_expiration(db, trx); @@ -801,8 +871,11 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { generate_block(); trx.clear(); - BOOST_CHECK( obj->status == son_status::in_maintenance); - BOOST_CHECK( son_stats_obj->last_down_timestamp == op.down_ts); + for (const auto& active_sidechain_type : active_sidechain_types(db.head_block_time())) + { + BOOST_CHECK( obj->statuses.at(active_sidechain_type) == son_status::in_maintenance); + BOOST_CHECK( son_stats_obj->last_down_timestamp.at(active_sidechain_type) == op.down_ts); + } } { @@ -812,7 +885,7 @@ BOOST_AUTO_TEST_CASE( son_report_down_test ) { son_report_down_operation op; op.payer = db.get_global_properties().parameters.son_account(); op.son_id = son_id_type(0); - op.down_ts = son_stats_obj->last_active_timestamp; + op.down_ts = son_stats_obj->last_active_timestamp.at(sidechain_type::bitcoin); trx.operations.push_back(op); set_expiration(db, trx); diff --git a/tests/tests/son_wallet_tests.cpp b/tests/tests/son_wallet_tests.cpp index cef29b54..fd0d65e8 100644 --- a/tests/tests/son_wallet_tests.cpp +++ b/tests/tests/son_wallet_tests.cpp @@ -154,6 +154,7 @@ BOOST_AUTO_TEST_CASE( son_wallet_recreate_test ) { op.payer = db.get_global_properties().parameters.son_account(); + //! Fixme - add hive tests { son_info si; si.son_id = son_id_type(0);