diff --git a/Dockerfile b/Dockerfile index 3f4c62b5..d49bbe08 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,5 +1,4 @@ FROM ubuntu:20.04 -MAINTAINER Peerplays Blockchain Standards Association #=============================================================================== # Ubuntu setup @@ -51,69 +50,102 @@ RUN echo 'peerplays:peerplays' | chpasswd # SSH EXPOSE 22 +WORKDIR /home/peerplays/src + #=============================================================================== # Boost setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ - wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \ - tar -xzvf boost_1_72_0.tar.gz boost_1_72_0 && \ - cd boost_1_72_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.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 && \ cmake .. && \ - make -j$(nproc) install && \ - ldconfig + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* #=============================================================================== # cppzmq 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 && \ + 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) install && \ - ldconfig + 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 https://github.com/libbitcoin/libbitcoin-build.git && \ + cd libbitcoin-build && \ + ./generate3.sh && \ + cd ../libbitcoin-explorer && \ + ./install.sh && \ + ldconfig && \ + rm -rf /home/peerplays/src/* #=============================================================================== # Doxygen setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ sudo apt install -y bison flex && \ wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \ @@ -129,8 +161,6 @@ RUN \ # Perl setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \ tar -xvf v5.30.0.tar.gz && \ @@ -143,8 +173,6 @@ RUN \ # Peerplays setup #=============================================================================== -WORKDIR /home/peerplays/ - ## Clone Peerplays #RUN \ # git clone https://gitlab.com/PBSA/peerplays.git && \ @@ -176,8 +204,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 2e153962..d09e01b2 100644 --- a/Dockerfile.18.04 +++ b/Dockerfile.18.04 @@ -1,5 +1,4 @@ FROM ubuntu:18.04 -MAINTAINER Peerplays Blockchain Standards Association #=============================================================================== # Ubuntu setup @@ -51,69 +50,102 @@ RUN echo 'peerplays:peerplays' | chpasswd # SSH EXPOSE 22 +WORKDIR /home/peerplays/src + #=============================================================================== # Boost setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ wget https://boostorg.jfrog.io/artifactory/main/release/1.72.0/source/boost_1_72_0.tar.gz && \ - tar -xzvf boost_1_72_0.tar.gz boost_1_72_0 && \ - cd boost_1_72_0/ && \ + 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.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 && \ cmake .. && \ - make -j$(nproc) install && \ - ldconfig + make -j$(nproc) && \ + make install && \ + ldconfig && \ + rm -rf /home/peerplays/src/* #=============================================================================== # cppzmq 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 && \ + 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) install && \ - ldconfig + 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 https://github.com/libbitcoin/libbitcoin-build.git && \ + cd libbitcoin-build && \ + ./generate3.sh && \ + cd ../libbitcoin-explorer && \ + ./install.sh && \ + ldconfig && \ + rm -rf /home/peerplays/src/* #=============================================================================== # Doxygen setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ sudo apt install -y bison flex && \ wget https://github.com/doxygen/doxygen/archive/refs/tags/Release_1_8_17.tar.gz && \ @@ -129,8 +161,6 @@ RUN \ # Perl setup #=============================================================================== -WORKDIR /home/peerplays/ - RUN \ wget https://github.com/Perl/perl5/archive/refs/tags/v5.30.0.tar.gz && \ tar -xvf v5.30.0.tar.gz && \ @@ -143,8 +173,6 @@ RUN \ # Peerplays setup #=============================================================================== -WORKDIR /home/peerplays/ - ## Clone Peerplays #RUN \ # git clone https://gitlab.com/PBSA/peerplays.git && \ @@ -176,8 +204,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 f3d8c5b9..c3533d29 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 'https://boostorg.jfrog.io/artifactory/main/release/1.71.0/source/boost_1_71_0.tar.bz2' -O boost_1_71_0.tar.bz2 -tar xjf boost_1_71_0.tar.bz2 -cd boost_1_71_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,50 @@ 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 https://github.com/libbitcoin/libbitcoin-build.git +cd libbitcoin-build +./generate3.sh +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 +131,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/app/database_api.cpp b/libraries/app/database_api.cpp index c4e69607..be60de78 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -2340,7 +2340,7 @@ votes_info database_api_impl::get_votes(const string &account_name_or_id) const 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()) { + 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}); } } diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt old mode 100755 new mode 100644 index 4f724602..21fac167 --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -16,6 +16,8 @@ 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 @@ -42,7 +44,7 @@ endif() unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS) unset(ENABLE_PEERPLAYS_ASSET_DEPOSITS CACHE) -target_link_libraries( peerplays_sidechain PRIVATE graphene_plugin sha3 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/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..10be2868 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin/estimate_fee_external.hpp @@ -0,0 +1,37 @@ +#pragma once + +#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/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/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp index 0e7e2fc1..836a1f05 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 @@ -59,6 +59,7 @@ protected: 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 e880a7ae..bfb93805 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 @@ -1,18 +1,20 @@ #pragma once -#include #include +#include #include #include #include #include -#include - #include + #include +#include +#include +#include namespace graphene { namespace peerplays_sidechain { @@ -23,7 +25,27 @@ public: uint64_t amount_; }; -class bitcoin_rpc_client : public 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, @@ -41,14 +63,47 @@ public: std::string label; }; + 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; + }; + + 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(std::string _url, std::string _user, std::string _password, bool _debug_rpc_calls); - std::string createwallet(const std::string &wallet_name); - uint64_t estimatesmartfee(uint16_t conf_target = 128); - 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(); + 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 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); @@ -60,35 +115,84 @@ public: private: std::string ip; - uint32_t rpc_port; std::string user; std::string password; std::string wallet_name; std::string wallet_password; + uint32_t bitcoin_major_version; +}; + +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(); + 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; +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(); - boost::signals2::signal event_received; private: void handle_zmq(); std::vector receive_multipart(); - std::string ip; - uint32_t zmq_port; - zmq::context_t ctx; zmq::socket_t socket; +}; - std::atomic_bool stopped; - std::thread thr; +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; }; // ============================================================================= @@ -109,16 +213,19 @@ public: virtual optional estimate_withdrawal_transaction_fee() const override; private: - std::string ip; - uint32_t zmq_port; + std::string bitcoin_node_ip; + std::string libbitcoin_server_ip; + uint32_t libbitcoin_block_zmq_port; + uint32_t libbitcoin_trx_zmq_port; + uint32_t bitcoin_node_zmq_port; uint32_t rpc_port; std::string rpc_user; std::string rpc_password; 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; @@ -138,9 +245,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/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index bd5bb504..e4a2b8ea 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -177,6 +177,10 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( cli.add_options()("debug-rpc-calls", bpo::value()->default_value(false), "Outputs RPC calls to console"); cli.add_options()("bitcoin-sidechain-enabled", bpo::value()->default_value(false), "Bitcoin sidechain handler enabled"); + 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-ip", bpo::value()->default_value("127.0.0.1"), "IP address of Bitcoin node"); 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"); @@ -192,7 +196,7 @@ void peerplays_sidechain_plugin_impl::plugin_set_program_options( 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()("erc-20-address", bpo::value>()->composing()->multitoken(), + 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)"); @@ -249,12 +253,14 @@ 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-name") && options.count("bitcoin-wallet-password") && - options.count("bitcoin-private-key"); - if (sidechain_enabled_bitcoin && !config_ready_bitcoin) { + + 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"); } diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp index 3f469cc8..f37352bb 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -29,11 +29,6 @@ bitcoin_rpc_client::bitcoin_rpc_client(std::string _url, std::string _user, std: rpc_client(_url, _user, _password, _debug_rpc_calls) { } -std::string bitcoin_rpc_client::createwallet(const std::string &wallet_name) { - const std::string params = std::string("[\"") + wallet_name + std::string("\"]"); - return send_post_request("createwallet", params, debug_rpc_calls); -} - uint64_t bitcoin_rpc_client::estimatesmartfee(uint16_t conf_target) { 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); @@ -58,36 +53,130 @@ uint64_t bitcoin_rpc_client::estimatesmartfee(uint16_t conf_target) { return 20000; } -std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t verbosity) { - const std::string params = std::string("[\"") + block_hash + std::string("\",") + std::to_string(verbosity) + std::string("]"); - return send_post_request("getblock", params, debug_rpc_calls); +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; + } + + 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); + } + } + } + + return result; } -std::string bitcoin_rpc_client::getnetworkinfo() { - static const std::string params = std::string("[]"); - return send_post_request("getnetworkinfo", params, debug_rpc_calls); +void bitcoin_rpc_client::getnetworkinfo() { + std::string params = std::string("[]"); + std::string str = send_post_request("getnetworkinfo", params, debug_rpc_calls); + + std::stringstream network_info_ss(str); + boost::property_tree::ptree network_info_json; + boost::property_tree::read_json(network_info_ss, network_info_json); + + 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::getrawtransaction(const std::string &txid, const bool verbose) { - const std::string params = std::string("[\"") + txid + std::string("\",") + (verbose ? "true" : "false") + std::string("]"); - return send_post_request("getrawtransaction", params, 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); + + 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); + } + } + + return tx; } std::string bitcoin_rpc_client::getblockchaininfo() { static const std::string params = std::string("[]"); const std::string str = send_post_request("getblockchaininfo", params, debug_rpc_calls); + std::string result; + if (str.length() > 0) { std::stringstream ss(str); boost::property_tree::ptree json; boost::property_tree::read_json(ss, json); - 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"); + } + } } - return str; + return result; } void bitcoin_rpc_client::importmulti(const std::vector &address_or_script_array, const bool rescan) { @@ -106,11 +195,11 @@ void bitcoin_rpc_client::importmulti(const std::vector &address_or //! 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.*/ + 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 += ", "; @@ -243,15 +332,178 @@ bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_ 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()); +} + +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); + } + } + + // 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 *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; +} // ============================================================================= -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), - stopped(false) { + socket(ctx, ZMQ_SUB) { } void zmq_listener::start() { @@ -265,16 +517,16 @@ void zmq_listener::start() { // 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)); + socket.connect("tcp://" + ip + ":" + std::to_string(block_zmq_port)); - thr = std::thread(&zmq_listener::handle_zmq, this); + block_thr = std::thread(&zmq_listener::handle_zmq, this); ilog("zmq_listener thread started"); } zmq_listener::~zmq_listener() { stopped = true; - thr.join(); + block_thr.join(); } std::vector zmq_listener::receive_multipart() { @@ -302,7 +554,9 @@ void zmq_listener::handle_zmq() { } 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); + 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())); @@ -314,6 +568,92 @@ void zmq_listener::handle_zmq() { // ============================================================================= +// ============================================================================= + +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; @@ -322,8 +662,16 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain debug_rpc_calls = options.at("debug-rpc-calls").as(); } - ip = options.at("bitcoin-node-ip").as(); - zmq_port = options.at("bitcoin-node-zmq-port").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(); + + bitcoin_node_ip = options.at("bitcoin-node-ip").as(); + bitcoin_node_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(); @@ -348,55 +696,44 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain } } - std::string url = ip + ":" + std::to_string(rpc_port); - if (!wallet_name.empty()) { - url = url + "/wallet/" + wallet_name; + if (use_bitcoind_client) { + std::string url = bitcoin_node_ip + ":" + std::to_string(rpc_port); + if (!wallet_name.empty()) { + url = url + "/wallet/" + wallet_name; + } + bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(url, rpc_user, rpc_password, debug_rpc_calls)); + if (!wallet_name.empty()) { + bitcoin_client->loadwallet(wallet_name); + } + + listener = std::unique_ptr(new zmq_listener(bitcoin_node_ip, 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(url, rpc_user, rpc_password, debug_rpc_calls)); - if (!wallet_name.empty()) { - bitcoin_client->loadwallet(wallet_name); - } - - std::string blockchain_info = bitcoin_client->getblockchaininfo(); - if (blockchain_info.empty()) { - elog("No Bitcoin node running at ${url}", ("url", url)); - FC_ASSERT(false); - } - 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); + std::string chain_info = bitcoin_client->getblockchaininfo(); using namespace bitcoin; network_type = bitcoin_address::network::mainnet; - if (bci_json.count("chain")) { - std::string chain = bci_json.get("chain"); - if (chain.length() > 0) { - 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(); - if (network_info_str.empty()) { - elog("No Bitcoin node running at ${url}", ("url", url)); - FC_ASSERT(false); - } - 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->start(); - 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(); }); database.changed_objects.connect([this](const vector &ids, const flat_set &accounts) { @@ -418,7 +755,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(sidechain))); + // ilog("Proposal to process: ${po}, SON id ${son_id}", ("po", po.id)("son_id", plugin.get_current_son_id(sidechain))); bool should_approve = false; @@ -450,7 +787,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) 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 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()) { @@ -491,7 +828,7 @@ bool sidechain_net_handler_bitcoin::process_proposal(const proposal_object &po) 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 }; + 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(); @@ -533,50 +870,35 @@ 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)); - const std::string tx_str = bitcoin_client->getrawtransaction(swdo_txid, true); - if (tx_str != "") { - 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_address = ""; - uint64_t tx_amount = -1; - uint64_t tx_vout = -1; + 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); - if (tx_vout == swdo_vout) { - if (bitcoin_major_version > 21) { - std::string address = input.second.get("scriptPubKey.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; - } - } + for (auto &input : tx.tx_in_list) { + tx_vout = input.tx_vout; + if (tx_vout == swdo_vout) { + for (auto &address : input.tx_address) { + if (address == swdo_address) { + tx_address = address; + 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); - break; } + tx_amount = input.tx_amount; + break; } - - process_ok = (swdo_txid == tx_txid) && - (swdo_address == tx_address) && - (swdo_amount == tx_amount) && - (swdo_vout == tx_vout) && - (gpo.parameters.son_bitcoin_min_tx_confirmations() <= tx_confirmations); } + + process_ok = (swdo_txid == tx_txid) && + (swdo_address == tx_address) && + (swdo_amount == tx_amount) && + (swdo_vout == tx_vout) && + (gpo.parameters.son_bitcoin_min_tx_confirmations() <= tx_confirmations); } object_id_type object_id = op_obj_idx_1.get().object_id; @@ -724,7 +1046,7 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { 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 }; + 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; @@ -765,7 +1087,7 @@ void sidechain_net_handler_bitcoin::process_primary_wallet() { 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 }; + 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(); @@ -820,7 +1142,12 @@ 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()) + "\" }"; @@ -977,61 +1304,53 @@ bool sidechain_net_handler_bitcoin::settle_sidechain_transaction(const sidechain return false; } - const std::string tx_str = bitcoin_client->getrawtransaction(sto.sidechain_transaction, true); - if (tx_str != "") { - 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())) { - return false; - } + if (tx.tx_in_list.empty()) { + // This case will result with segmentation fault. + // FIXME check if that happened before introducing libbitcoin + return false; + } - const chain::global_property_object &gpo = database.get_global_properties(); + const chain::global_property_object &gpo = database.get_global_properties(); - using namespace bitcoin; - std::vector> pubkey_weights; - for (auto si : sto.signers) { - 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); + using namespace bitcoin; + std::vector> pubkey_weights; + for (auto si : sto.signers) { + 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)); + } - std::string tx_txid = tx_json.get("result.txid"); - uint32_t tx_confirmations = tx_json.get("result.confirmations"); - std::string tx_address = addr.get_address(); - int64_t tx_amount = -1; + payment_type payment_type_address = payment_type::P2SH_WSH; + if (use_bitcoind_client) { + payment_type_address = payment_type::P2WSH; + } - 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"); - 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; - } - } + 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.tx_in_list) { + for (auto &address : input.tx_address) { + if (address == tx_address) { + tx_amount = input.tx_amount; + break; } } settle_amount = asset(tx_amount, database.get_global_properties().parameters.btc_asset()); return true; } + } - if (sto.object_id.is()) { - auto swwo = database.get(sto.object_id); - settle_amount = asset(swwo.withdraw_amount, database.get_global_properties().parameters.btc_asset()); - return true; - } + if (sto.object_id.is()) { + auto swwo = database.get(sto.object_id); + settle_amount = asset(swwo.withdraw_amount, database.get_global_properties().parameters.btc_asset()); + return true; } } return false; @@ -1051,7 +1370,11 @@ std::string sidechain_net_handler_bitcoin::create_primary_wallet_address(const s 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; @@ -1287,18 +1610,34 @@ std::string sidechain_net_handler_bitcoin::send_transaction(const sidechain_tran } // 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)); - return bitcoin_client->sendrawtransaction(final_tx_hex); + std::string res = bitcoin_client->sendrawtransaction(final_tx_hex); + + if (res.empty()) { + return res; + } + + return tx.get_txid().str(); } -void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { - const 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(); @@ -1335,6 +1674,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(); @@ -1355,56 +1698,14 @@ std::string sidechain_net_handler_bitcoin::get_redeemscript_for_userdeposit(cons 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 json; - boost::property_tree::read_json(ss, json); - - auto json_result = json.get_child_optional("result"); - - std::vector 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"); - - 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) { diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_ethereum.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_ethereum.cpp index d0051714..40bf8481 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_ethereum.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_ethereum.cpp @@ -148,8 +148,8 @@ sidechain_net_handler_ethereum::sidechain_net_handler_ethereum(peerplays_sidecha wallet_contract_address = options.at("ethereum-wallet-contract-address").as(); - if (options.count("erc-20-address")) { - const std::vector symbol_addresses = options["erc-20-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)); @@ -236,7 +236,7 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po) 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 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()) { @@ -259,7 +259,7 @@ bool sidechain_net_handler_ethereum::process_proposal(const proposal_object &po) 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 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(); @@ -459,7 +459,7 @@ void sidechain_net_handler_ethereum::process_primary_wallet() { 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 }; + 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; diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp index 3f1378e7..6ad95850 100644 --- a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_hive.cpp @@ -211,7 +211,7 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { 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 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()) { @@ -234,7 +234,7 @@ bool sidechain_net_handler_hive::process_proposal(const proposal_object &po) { 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 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(); @@ -493,7 +493,7 @@ void sidechain_net_handler_hive::process_primary_wallet() { 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 }; + 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;