aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--Dockerfile57
-rw-r--r--LICENSE10
-rw-r--r--contrib/epee/include/net/network_throttle-detail.hpp3
-rw-r--r--contrib/epee/include/net/network_throttle.hpp3
-rw-r--r--contrib/epee/include/storages/portable_storage_base.h1
-rw-r--r--contrib/epee/src/network_throttle-detail.cpp10
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp159
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.h3
-rw-r--r--src/blockchain_utilities/blockchain_import.cpp21
-rw-r--r--src/blockchain_utilities/bootstrap_file.cpp11
-rw-r--r--src/blockchain_utilities/bootstrap_file.h2
-rw-r--r--src/blockchain_utilities/bootstrap_serialization.h21
-rw-r--r--src/common/dns_utils.cpp10
-rw-r--r--src/common/util.cpp36
-rw-r--r--src/common/util.h2
-rw-r--r--src/cryptonote_basic/cryptonote_boost_serialization.h29
-rw-r--r--src/cryptonote_basic/difficulty.cpp83
-rw-r--r--src/cryptonote_basic/difficulty.h7
-rw-r--r--src/cryptonote_basic/miner.cpp5
-rw-r--r--src/cryptonote_core/blockchain.cpp54
-rw-r--r--src/cryptonote_core/blockchain.h4
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp18
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_defs.h5
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl4
-rw-r--r--src/daemon/command_parser_executor.cpp7
-rw-r--r--src/daemon/command_parser_executor.h2
-rw-r--r--src/daemon/command_server.cpp5
-rw-r--r--src/daemon/daemon.cpp2
-rw-r--r--src/daemon/rpc_command_executor.cpp60
-rw-r--r--src/daemon/rpc_command_executor.h2
-rw-r--r--src/mnemonics/electrum-words.cpp4
-rw-r--r--src/p2p/net_node.inl6
-rw-r--r--src/ringct/rctOps.cpp6
-rw-r--r--src/rpc/core_rpc_server.cpp54
-rw-r--r--src/rpc/core_rpc_server.h2
-rw-r--r--src/rpc/core_rpc_server_commands_defs.h348
-rw-r--r--src/rpc/daemon_handler.cpp9
-rw-r--r--src/rpc/message_data_structs.h4
-rw-r--r--src/serialization/difficulty_type.h65
-rw-r--r--src/simplewallet/simplewallet.cpp10
-rw-r--r--src/wallet/wallet2.cpp61
-rw-r--r--src/wallet/wallet2.h7
-rw-r--r--src/wallet/wallet_light_rpc.h320
-rw-r--r--src/wallet/wallet_rpc_server.cpp97
-rw-r--r--src/wallet/wallet_rpc_server.h4
-rw-r--r--src/wallet/wallet_rpc_server_commands_defs.h30
-rw-r--r--tests/README.md4
-rw-r--r--tests/block_weight/block_weight.cpp5
-rw-r--r--tests/difficulty/CMakeLists.txt3
-rw-r--r--tests/difficulty/difficulty.cpp64
-rw-r--r--tests/difficulty/gen_wide_data.py47
-rwxr-xr-xtests/difficulty/wide_difficulty.py22
-rw-r--r--tests/functional_tests/CMakeLists.txt7
-rwxr-xr-xtests/functional_tests/blockchain.py158
-rwxr-xr-xtests/functional_tests/cold_signing.py146
-rwxr-xr-xtests/functional_tests/daemon_info.py89
-rwxr-xr-xtests/functional_tests/functional_tests_rpc.py135
-rwxr-xr-xtests/functional_tests/integrated_address.py101
-rwxr-xr-xtests/functional_tests/mining.py123
-rwxr-xr-xtests/functional_tests/multisig.py227
-rwxr-xr-xtests/functional_tests/proofs.py282
-rwxr-xr-xtests/functional_tests/sign_message.py85
-rwxr-xr-xtests/functional_tests/speed.py6
-rw-r--r--tests/functional_tests/test_framework/daemon.py105
-rw-r--r--tests/functional_tests/test_framework/wallet.py120
-rwxr-xr-xtests/functional_tests/transfer.py487
-rwxr-xr-xtests/functional_tests/txpool.py156
-rwxr-xr-xtests/functional_tests/wallet_address.py152
-rw-r--r--tests/hash-target.cpp12
-rw-r--r--tests/performance_tests/CMakeLists.txt1
-rw-r--r--tests/performance_tests/check_hash.h66
-rw-r--r--tests/performance_tests/main.cpp9
-rw-r--r--tests/unit_tests/CMakeLists.txt1
-rw-r--r--tests/unit_tests/difficulty.cpp68
-rw-r--r--tests/unit_tests/serialization.cpp23
-rwxr-xr-xutils/python-rpc/console.py49
-rw-r--r--utils/python-rpc/framework/__init__.py (renamed from tests/functional_tests/test_framework/__init__.py)0
-rw-r--r--utils/python-rpc/framework/daemon.py219
-rw-r--r--utils/python-rpc/framework/rpc.py (renamed from tests/functional_tests/test_framework/rpc.py)40
-rw-r--r--utils/python-rpc/framework/wallet.py600
80 files changed, 4520 insertions, 755 deletions
diff --git a/Dockerfile b/Dockerfile
index d932e0173..999290c95 100644
--- a/Dockerfile
+++ b/Dockerfile
@@ -25,10 +25,13 @@ RUN set -ex && \
WORKDIR /usr/local
+ENV CFLAGS='-fPIC'
+ENV CXXFLAGS='-fPIC'
+
#Cmake
-ARG CMAKE_VERSION=3.13.0
-ARG CMAKE_VERSION_DOT=v3.13
-ARG CMAKE_HASH=4058b2f1a53c026564e8936698d56c3b352d90df067b195cb749a97a3d273c90
+ARG CMAKE_VERSION=3.14.0
+ARG CMAKE_VERSION_DOT=v3.14
+ARG CMAKE_HASH=aa76ba67b3c2af1946701f847073f4652af5cbd9f141f221c97af99127e75502
RUN set -ex \
&& curl -s -O https://cmake.org/files/${CMAKE_VERSION_DOT}/cmake-${CMAKE_VERSION}.tar.gz \
&& echo "${CMAKE_HASH} cmake-${CMAKE_VERSION}.tar.gz" | sha256sum -c \
@@ -39,41 +42,41 @@ RUN set -ex \
&& make install
## Boost
-ARG BOOST_VERSION=1_68_0
-ARG BOOST_VERSION_DOT=1.68.0
-ARG BOOST_HASH=7f6130bc3cf65f56a618888ce9d5ea704fa10b462be126ad053e80e553d6d8b7
+ARG BOOST_VERSION=1_69_0
+ARG BOOST_VERSION_DOT=1.69.0
+ARG BOOST_HASH=8f32d4617390d1c2d16f26a27ab60d97807b35440d45891fa340fc2648b04406
RUN set -ex \
&& curl -s -L -o boost_${BOOST_VERSION}.tar.bz2 https://dl.bintray.com/boostorg/release/${BOOST_VERSION_DOT}/source/boost_${BOOST_VERSION}.tar.bz2 \
&& echo "${BOOST_HASH} boost_${BOOST_VERSION}.tar.bz2" | sha256sum -c \
&& tar -xvf boost_${BOOST_VERSION}.tar.bz2 \
&& cd boost_${BOOST_VERSION} \
&& ./bootstrap.sh \
- && ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale threading=multi threadapi=pthread cflags="-fPIC" cxxflags="-fPIC" stage
+ && ./b2 --build-type=minimal link=static runtime-link=static --with-chrono --with-date_time --with-filesystem --with-program_options --with-regex --with-serialization --with-system --with-thread --with-locale threading=multi threadapi=pthread cflags="$CFLAGS" cxxflags="$CXXFLAGS" stage
ENV BOOST_ROOT /usr/local/boost_${BOOST_VERSION}
# OpenSSL
-ARG OPENSSL_VERSION=1.1.0j
-ARG OPENSSL_HASH=31bec6c203ce1a8e93d5994f4ed304c63ccf07676118b6634edded12ad1b3246
+ARG OPENSSL_VERSION=1.1.1b
+ARG OPENSSL_HASH=5c557b023230413dfb0756f3137a13e6d726838ccd1430888ad15bfb2b43ea4b
RUN set -ex \
&& curl -s -O https://www.openssl.org/source/openssl-${OPENSSL_VERSION}.tar.gz \
&& echo "${OPENSSL_HASH} openssl-${OPENSSL_VERSION}.tar.gz" | sha256sum -c \
&& tar -xzf openssl-${OPENSSL_VERSION}.tar.gz \
&& cd openssl-${OPENSSL_VERSION} \
- && ./Configure linux-x86_64 no-shared --static -fPIC \
+ && ./Configure linux-x86_64 no-shared --static "$CFLAGS" \
&& make build_generated \
&& make libcrypto.a \
&& make install
ENV OPENSSL_ROOT_DIR=/usr/local/openssl-${OPENSSL_VERSION}
# ZMQ
-ARG ZMQ_VERSION=v4.2.5
-ARG ZMQ_HASH=d062edd8c142384792955796329baf1e5a3377cd
+ARG ZMQ_VERSION=v4.3.1
+ARG ZMQ_HASH=2cb1240db64ce1ea299e00474c646a2453a8435b
RUN set -ex \
&& git clone https://github.com/zeromq/libzmq.git -b ${ZMQ_VERSION} \
&& cd libzmq \
&& test `git rev-parse HEAD` = ${ZMQ_HASH} || exit 1 \
&& ./autogen.sh \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure --enable-static --disable-shared \
+ && ./configure --enable-static --disable-shared \
&& make \
&& make install \
&& ldconfig
@@ -88,39 +91,39 @@ RUN set -ex \
&& mv *.hpp /usr/local/include
# Readline
-ARG READLINE_VERSION=7.0
-ARG READLINE_HASH=750d437185286f40a369e1e4f4764eda932b9459b5ec9a731628393dd3d32334
+ARG READLINE_VERSION=8.0
+ARG READLINE_HASH=e339f51971478d369f8a053a330a190781acb9864cf4c541060f12078948e461
RUN set -ex \
&& curl -s -O https://ftp.gnu.org/gnu/readline/readline-${READLINE_VERSION}.tar.gz \
&& echo "${READLINE_HASH} readline-${READLINE_VERSION}.tar.gz" | sha256sum -c \
&& tar -xzf readline-${READLINE_VERSION}.tar.gz \
&& cd readline-${READLINE_VERSION} \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure \
+ && ./configure \
&& make \
&& make install
# Sodium
-ARG SODIUM_VERSION=1.0.16
-ARG SODIUM_HASH=675149b9b8b66ff44152553fb3ebf9858128363d
+ARG SODIUM_VERSION=1.0.17
+ARG SODIUM_HASH=b732443c442239c2e0184820e9b23cca0de0828c
RUN set -ex \
&& git clone https://github.com/jedisct1/libsodium.git -b ${SODIUM_VERSION} \
&& cd libsodium \
&& test `git rev-parse HEAD` = ${SODIUM_HASH} || exit 1 \
&& ./autogen.sh \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure \
+ && ./configure \
&& make \
&& make check \
&& make install
# Udev
-ARG UDEV_VERSION=v3.2.6
-ARG UDEV_HASH=0c35b136c08d64064efa55087c54364608e65ed6
+ARG UDEV_VERSION=v3.2.7
+ARG UDEV_HASH=4758e346a14126fc3a964de5831e411c27ebe487
RUN set -ex \
&& git clone https://github.com/gentoo/eudev -b ${UDEV_VERSION} \
&& cd eudev \
&& test `git rev-parse HEAD` = ${UDEV_HASH} || exit 1 \
&& ./autogen.sh \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure --disable-gudev --disable-introspection --disable-hwdb --disable-manpages --disable-shared \
+ && ./configure --disable-gudev --disable-introspection --disable-hwdb --disable-manpages --disable-shared \
&& make \
&& make install
@@ -132,7 +135,7 @@ RUN set -ex \
&& cd libusb \
&& test `git rev-parse HEAD` = ${USB_HASH} || exit 1 \
&& ./autogen.sh \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure --disable-shared \
+ && ./configure --disable-shared \
&& make \
&& make install
@@ -144,20 +147,20 @@ RUN set -ex \
&& cd hidapi \
&& test `git rev-parse HEAD` = ${HIDAPI_HASH} || exit 1 \
&& ./bootstrap \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure --enable-static --disable-shared \
+ && ./configure --enable-static --disable-shared \
&& make \
&& make install
# Protobuf
-ARG PROTOBUF_VERSION=v3.6.1
-ARG PROTOBUF_HASH=48cb18e5c419ddd23d9badcfe4e9df7bde1979b2
+ARG PROTOBUF_VERSION=v3.7.0
+ARG PROTOBUF_HASH=582743bf40c5d3639a70f98f183914a2c0cd0680
RUN set -ex \
&& git clone https://github.com/protocolbuffers/protobuf -b ${PROTOBUF_VERSION} \
&& cd protobuf \
&& test `git rev-parse HEAD` = ${PROTOBUF_HASH} || exit 1 \
&& git submodule update --init --recursive \
&& ./autogen.sh \
- && CFLAGS="-fPIC" CXXFLAGS="-fPIC" ./configure --enable-static --disable-shared \
+ && ./configure --enable-static --disable-shared \
&& make \
&& make install \
&& ldconfig
diff --git a/LICENSE b/LICENSE
index f48fcda87..7035c2324 100644
--- a/LICENSE
+++ b/LICENSE
@@ -29,3 +29,13 @@ OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
Parts of the project are originally copyright (c) 2012-2013 The Cryptonote
developers
+
+Parts of the project are originally copyright (c) 2014 The Boolberry
+developers, distributed under the MIT licence:
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+
diff --git a/contrib/epee/include/net/network_throttle-detail.hpp b/contrib/epee/include/net/network_throttle-detail.hpp
index d7f2cc37a..353ae0c0c 100644
--- a/contrib/epee/include/net/network_throttle-detail.hpp
+++ b/contrib/epee/include/net/network_throttle-detail.hpp
@@ -66,6 +66,8 @@ class network_throttle : public i_network_throttle {
network_time_seconds m_last_sample_time; // time of last history[0] - so we know when to rotate the buffer
network_time_seconds m_start_time; // when we were created
bool m_any_packet_yet; // did we yet got any packet to count
+ uint64_t m_total_packets;
+ uint64_t m_total_bytes;
std::string m_name; // my name for debug and logs
std::string m_nameshort; // my name for debug and logs (used in log file name)
@@ -95,6 +97,7 @@ class network_throttle : public i_network_throttle {
virtual size_t get_recommended_size_of_planned_transport() const; ///< what should be the size (bytes) of next data block to be transported
virtual size_t get_recommended_size_of_planned_transport_window(double force_window) const; ///< ditto, but for given windows time frame
virtual double get_current_speed() const;
+ virtual void get_stats(uint64_t &total_packets, uint64_t &total_bytes) const;
private:
virtual network_time_seconds time_to_slot(network_time_seconds t) const { return std::floor( t ); } // convert exact time eg 13.7 to rounded time for slot number in history 13
diff --git a/contrib/epee/include/net/network_throttle.hpp b/contrib/epee/include/net/network_throttle.hpp
index 5092241a4..02a286326 100644
--- a/contrib/epee/include/net/network_throttle.hpp
+++ b/contrib/epee/include/net/network_throttle.hpp
@@ -152,7 +152,8 @@ class i_network_throttle {
virtual size_t get_recommended_size_of_planned_transport() const =0; // what should be the recommended limit of data size that we can transport over current network_throttle in near future
virtual double get_time_seconds() const =0; // a timer
- virtual void logger_handle_net(const std::string &filename, double time, size_t size)=0;
+ virtual void logger_handle_net(const std::string &filename, double time, size_t size)=0;
+ virtual void get_stats(uint64_t &total_packets, uint64_t &total_bytes) const =0;
};
diff --git a/contrib/epee/include/storages/portable_storage_base.h b/contrib/epee/include/storages/portable_storage_base.h
index da84fd8ea..ca7c81ddc 100644
--- a/contrib/epee/include/storages/portable_storage_base.h
+++ b/contrib/epee/include/storages/portable_storage_base.h
@@ -82,6 +82,7 @@ namespace epee
struct array_entry_t
{
array_entry_t():m_it(m_array.end()){}
+ array_entry_t(const array_entry_t& other):m_array(other.m_array), m_it(m_array.end()){}
const t_entry_type* get_first_val() const
{
diff --git a/contrib/epee/src/network_throttle-detail.cpp b/contrib/epee/src/network_throttle-detail.cpp
index f89e7aec0..72544cbf6 100644
--- a/contrib/epee/src/network_throttle-detail.cpp
+++ b/contrib/epee/src/network_throttle-detail.cpp
@@ -136,6 +136,8 @@ network_throttle::network_throttle(const std::string &nameshort, const std::stri
m_target_speed = 16 * 1024; // other defaults are probably defined in the command-line parsing code when this class is used e.g. as main global throttle
m_last_sample_time = 0;
m_history.resize(m_window_size);
+ m_total_packets = 0;
+ m_total_bytes = 0;
}
void network_throttle::set_name(const std::string &name)
@@ -192,6 +194,8 @@ void network_throttle::_handle_trafic_exact(size_t packet_size, size_t orginal_s
calculate_times_struct cts ; calculate_times(packet_size, cts , false, -1);
calculate_times_struct cts2; calculate_times(packet_size, cts2, false, 5);
m_history.front().m_size += packet_size;
+ m_total_packets++;
+ m_total_bytes += packet_size;
std::ostringstream oss; oss << "["; for (auto sample: m_history) oss << sample.m_size << " "; oss << "]" << std::ends;
std::string history_str = oss.str();
@@ -352,6 +356,12 @@ double network_throttle::get_current_speed() const {
return bytes_transferred / ((m_history.size() - 1) * m_slot_size);
}
+void network_throttle::get_stats(uint64_t &total_packets, uint64_t &total_bytes) const {
+ total_packets = m_total_packets;
+ total_bytes = m_total_bytes;
+}
+
+
} // namespace
} // namespace
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 3391d9bff..9f71fd068 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -54,7 +54,7 @@ using epee::string_tools::pod_to_hex;
using namespace crypto;
// Increase when the DB structure changes
-#define VERSION 4
+#define VERSION 5
namespace
{
@@ -274,7 +274,7 @@ typedef struct mdb_block_info_1
uint64_t bi_timestamp;
uint64_t bi_coins;
uint64_t bi_weight; // a size_t really but we need 32-bit compat
- difficulty_type bi_diff;
+ uint64_t bi_diff;
crypto::hash bi_hash;
} mdb_block_info_1;
@@ -284,7 +284,7 @@ typedef struct mdb_block_info_2
uint64_t bi_timestamp;
uint64_t bi_coins;
uint64_t bi_weight; // a size_t really but we need 32-bit compat
- difficulty_type bi_diff;
+ uint64_t bi_diff;
crypto::hash bi_hash;
uint64_t bi_cum_rct;
} mdb_block_info_2;
@@ -295,13 +295,26 @@ typedef struct mdb_block_info_3
uint64_t bi_timestamp;
uint64_t bi_coins;
uint64_t bi_weight; // a size_t really but we need 32-bit compat
- difficulty_type bi_diff;
+ uint64_t bi_diff;
crypto::hash bi_hash;
uint64_t bi_cum_rct;
uint64_t bi_long_term_block_weight;
} mdb_block_info_3;
-typedef mdb_block_info_3 mdb_block_info;
+typedef struct mdb_block_info_4
+{
+ uint64_t bi_height;
+ uint64_t bi_timestamp;
+ uint64_t bi_coins;
+ uint64_t bi_weight; // a size_t really but we need 32-bit compat
+ uint64_t bi_diff_lo;
+ uint64_t bi_diff_hi;
+ crypto::hash bi_hash;
+ uint64_t bi_cum_rct;
+ uint64_t bi_long_term_block_weight;
+} mdb_block_info_4;
+
+typedef mdb_block_info_4 mdb_block_info;
typedef struct blk_height {
crypto::hash bh_hash;
@@ -757,7 +770,8 @@ void BlockchainLMDB::add_block(const block& blk, size_t block_weight, uint64_t l
bi.bi_timestamp = blk.timestamp;
bi.bi_coins = coins_generated;
bi.bi_weight = block_weight;
- bi.bi_diff = cumulative_difficulty;
+ bi.bi_diff_hi = (cumulative_difficulty >> 64).convert_to<uint64_t>();
+ bi.bi_diff_lo = (cumulative_difficulty << 64 >> 64).convert_to<uint64_t>();
bi.bi_hash = blk_hash;
bi.bi_cum_rct = num_rct_outs;
if (blk.major_version >= 4)
@@ -2527,7 +2541,9 @@ difficulty_type BlockchainLMDB::get_block_cumulative_difficulty(const uint64_t&
throw0(DB_ERROR("Error attempting to retrieve a cumulative difficulty from the db"));
mdb_block_info *bi = (mdb_block_info *)result.mv_data;
- difficulty_type ret = bi->bi_diff;
+ difficulty_type ret = bi->bi_diff_hi;
+ ret <<= 64;
+ ret |= bi->bi_diff_lo;
TXN_POSTFIX_RDONLY();
return ret;
}
@@ -5040,6 +5056,133 @@ void BlockchainLMDB::migrate_3_4()
txn.commit();
}
+void BlockchainLMDB::migrate_4_5()
+{
+ LOG_PRINT_L3("BlockchainLMDB::" << __func__);
+ uint64_t i;
+ int result;
+ mdb_txn_safe txn(false);
+ MDB_val k, v;
+ char *ptr;
+
+ MGINFO_YELLOW("Migrating blockchain from DB version 4 to 5 - this may take a while:");
+
+ do {
+ LOG_PRINT_L1("migrating block info:");
+
+ result = mdb_txn_begin(m_env, NULL, 0, txn);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
+
+ MDB_stat db_stats;
+ if ((result = mdb_stat(txn, m_blocks, &db_stats)))
+ throw0(DB_ERROR(lmdb_error("Failed to query m_blocks: ", result).c_str()));
+ const uint64_t blockchain_height = db_stats.ms_entries;
+
+ /* the block_info table name is the same but the old version and new version
+ * have incompatible data. Create a new table. We want the name to be similar
+ * to the old name so that it will occupy the same location in the DB.
+ */
+ MDB_dbi o_block_info = m_block_info;
+ lmdb_db_open(txn, "block_infn", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for block_infn");
+ mdb_set_dupsort(txn, m_block_info, compare_uint64);
+
+
+ MDB_cursor *c_blocks;
+ result = mdb_cursor_open(txn, m_blocks, &c_blocks);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for blocks: ", result).c_str()));
+
+ MDB_cursor *c_old, *c_cur;
+ i = 0;
+ while(1) {
+ if (!(i % 1000)) {
+ if (i) {
+ LOGIF(el::Level::Info) {
+ std::cout << i << " / " << blockchain_height << " \r" << std::flush;
+ }
+ txn.commit();
+ result = mdb_txn_begin(m_env, NULL, 0, txn);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
+ }
+ result = mdb_cursor_open(txn, m_block_info, &c_cur);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_infn: ", result).c_str()));
+ result = mdb_cursor_open(txn, o_block_info, &c_old);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to open a cursor for block_info: ", result).c_str()));
+ if (!i) {
+ MDB_stat db_stat;
+ result = mdb_stat(txn, m_block_info, &db_stats);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to query m_block_info: ", result).c_str()));
+ i = db_stats.ms_entries;
+ }
+ }
+ result = mdb_cursor_get(c_old, &k, &v, MDB_NEXT);
+ if (result == MDB_NOTFOUND) {
+ txn.commit();
+ break;
+ }
+ else if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to get a record from block_info: ", result).c_str()));
+ const mdb_block_info_3 *bi_old = (const mdb_block_info_3*)v.mv_data;
+ mdb_block_info_4 bi;
+ bi.bi_height = bi_old->bi_height;
+ bi.bi_timestamp = bi_old->bi_timestamp;
+ bi.bi_coins = bi_old->bi_coins;
+ bi.bi_weight = bi_old->bi_weight;
+ bi.bi_diff_lo = bi_old->bi_diff;
+ bi.bi_diff_hi = 0;
+ bi.bi_hash = bi_old->bi_hash;
+ bi.bi_cum_rct = bi_old->bi_cum_rct;
+ bi.bi_long_term_block_weight = bi_old->bi_long_term_block_weight;
+
+ MDB_val_set(nv, bi);
+ result = mdb_cursor_put(c_cur, (MDB_val *)&zerokval, &nv, MDB_APPENDDUP);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to put a record into block_infn: ", result).c_str()));
+ /* we delete the old records immediately, so the overall DB and mapsize should not grow.
+ * This is a little slower than just letting mdb_drop() delete it all at the end, but
+ * it saves a significant amount of disk space.
+ */
+ result = mdb_cursor_del(c_old, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete a record from block_info: ", result).c_str()));
+ i++;
+ }
+
+ result = mdb_txn_begin(m_env, NULL, 0, txn);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
+ /* Delete the old table */
+ result = mdb_drop(txn, o_block_info, 1);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to delete old block_info table: ", result).c_str()));
+
+ RENAME_DB("block_infn");
+ mdb_dbi_close(m_env, m_block_info);
+
+ lmdb_db_open(txn, "block_info", MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_info, "Failed to open db handle for block_infn");
+ mdb_set_dupsort(txn, m_block_info, compare_uint64);
+
+ txn.commit();
+ } while(0);
+
+ uint32_t version = 5;
+ v.mv_data = (void *)&version;
+ v.mv_size = sizeof(version);
+ MDB_val_str(vk, "version");
+ result = mdb_txn_begin(m_env, NULL, 0, txn);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str()));
+ result = mdb_put(txn, m_properties, &vk, &v, 0);
+ if (result)
+ throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str()));
+ txn.commit();
+}
+
void BlockchainLMDB::migrate(const uint32_t oldversion)
{
if (oldversion < 1)
@@ -5050,6 +5193,8 @@ void BlockchainLMDB::migrate(const uint32_t oldversion)
migrate_2_3();
if (oldversion < 4)
migrate_3_4();
+ if (oldversion < 5)
+ migrate_4_5();
}
} // namespace cryptonote
diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h
index 9185bd409..2f89b77ac 100644
--- a/src/blockchain_db/lmdb/db_lmdb.h
+++ b/src/blockchain_db/lmdb/db_lmdb.h
@@ -418,6 +418,9 @@ private:
// migrate from DB version 3 to 4
void migrate_3_4();
+ // migrate from DB version 4 to 5
+ void migrate_4_5();
+
void cleanup_batch();
private:
diff --git a/src/blockchain_utilities/blockchain_import.cpp b/src/blockchain_utilities/blockchain_import.cpp
index e4efdc3cb..8454595ac 100644
--- a/src/blockchain_utilities/blockchain_import.cpp
+++ b/src/blockchain_utilities/blockchain_import.cpp
@@ -294,7 +294,8 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
}
// 4 byte magic + (currently) 1024 byte header structures
- bootstrap.seek_to_first_chunk(import_file);
+ uint8_t major_version, minor_version;
+ bootstrap.seek_to_first_chunk(import_file, major_version, minor_version);
std::string str1;
char buffer1[1024];
@@ -415,7 +416,23 @@ int import_from_file(cryptonote::core& core, const std::string& import_file_path
{
str1.assign(buffer_block, chunk_size);
bootstrap::block_package bp;
- if (! ::serialization::parse_binary(str1, bp))
+ bool res;
+ if (major_version == 0)
+ {
+ bootstrap::block_package_1 bp1;
+ res = ::serialization::parse_binary(str1, bp1);
+ if (res)
+ {
+ bp.block = std::move(bp1.block);
+ bp.txs = std::move(bp1.txs);
+ bp.block_weight = bp1.block_weight;
+ bp.cumulative_difficulty = bp1.cumulative_difficulty;
+ bp.coins_generated = bp1.coins_generated;
+ }
+ }
+ else
+ res = ::serialization::parse_binary(str1, bp);
+ if (!res)
throw std::runtime_error("Error in deserialization of chunk");
int display_interval = 1000;
diff --git a/src/blockchain_utilities/bootstrap_file.cpp b/src/blockchain_utilities/bootstrap_file.cpp
index fb9a24f5d..252c79776 100644
--- a/src/blockchain_utilities/bootstrap_file.cpp
+++ b/src/blockchain_utilities/bootstrap_file.cpp
@@ -124,8 +124,8 @@ bool BootstrapFile::initialize_file()
*m_raw_data_file << blob;
bootstrap::file_info bfi;
- bfi.major_version = 0;
- bfi.minor_version = 1;
+ bfi.major_version = 1;
+ bfi.minor_version = 0;
bfi.header_size = header_size;
bootstrap::blocks_info bbi;
@@ -323,7 +323,7 @@ bool BootstrapFile::store_blockchain_raw(Blockchain* _blockchain_storage, tx_mem
return BootstrapFile::close();
}
-uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file)
+uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version)
{
uint32_t file_magic;
@@ -371,6 +371,8 @@ uint64_t BootstrapFile::seek_to_first_chunk(std::ifstream& import_file)
uint64_t full_header_size = sizeof(file_magic) + bfi.header_size;
import_file.seekg(full_header_size);
+ major_version = bfi.major_version;
+ minor_version = bfi.minor_version;
return full_header_size;
}
@@ -461,7 +463,8 @@ uint64_t BootstrapFile::count_blocks(const std::string& import_file_path, std::s
}
uint64_t full_header_size; // 4 byte magic + length of header structures
- full_header_size = seek_to_first_chunk(import_file);
+ uint8_t major_version, minor_version;
+ full_header_size = seek_to_first_chunk(import_file, major_version, minor_version);
MINFO("Scanning blockchain from bootstrap file...");
bool quit = false;
diff --git a/src/blockchain_utilities/bootstrap_file.h b/src/blockchain_utilities/bootstrap_file.h
index 5fb2cf366..1e6ef5d81 100644
--- a/src/blockchain_utilities/bootstrap_file.h
+++ b/src/blockchain_utilities/bootstrap_file.h
@@ -60,7 +60,7 @@ public:
uint64_t count_bytes(std::ifstream& import_file, uint64_t blocks, uint64_t& h, bool& quit);
uint64_t count_blocks(const std::string& dir_path, std::streampos& start_pos, uint64_t& seek_height);
uint64_t count_blocks(const std::string& dir_path);
- uint64_t seek_to_first_chunk(std::ifstream& import_file);
+ uint64_t seek_to_first_chunk(std::ifstream& import_file, uint8_t &major_version, uint8_t &minor_version);
bool store_blockchain_raw(cryptonote::Blockchain* cs, cryptonote::tx_memory_pool* txp,
boost::filesystem::path& output_file, uint64_t use_block_height=0);
diff --git a/src/blockchain_utilities/bootstrap_serialization.h b/src/blockchain_utilities/bootstrap_serialization.h
index 554c6d56e..70b3eea7e 100644
--- a/src/blockchain_utilities/bootstrap_serialization.h
+++ b/src/blockchain_utilities/bootstrap_serialization.h
@@ -29,7 +29,7 @@
#pragma once
#include "cryptonote_basic/cryptonote_boost_serialization.h"
-#include "cryptonote_basic/difficulty.h"
+#include "serialization/difficulty_type.h"
namespace cryptonote
@@ -66,6 +66,23 @@ namespace cryptonote
END_SERIALIZE()
};
+ struct block_package_1
+ {
+ cryptonote::block block;
+ std::vector<transaction> txs;
+ size_t block_weight;
+ uint64_t cumulative_difficulty;
+ uint64_t coins_generated;
+
+ BEGIN_SERIALIZE()
+ FIELD(block)
+ FIELD(txs)
+ VARINT_FIELD(block_weight)
+ VARINT_FIELD(cumulative_difficulty)
+ VARINT_FIELD(coins_generated)
+ END_SERIALIZE()
+ };
+
struct block_package
{
cryptonote::block block;
@@ -78,7 +95,7 @@ namespace cryptonote
FIELD(block)
FIELD(txs)
VARINT_FIELD(block_weight)
- VARINT_FIELD(cumulative_difficulty)
+ FIELD(cumulative_difficulty)
VARINT_FIELD(coins_generated)
END_SERIALIZE()
};
diff --git a/src/common/dns_utils.cpp b/src/common/dns_utils.cpp
index a341a8c81..1a1155c7c 100644
--- a/src/common/dns_utils.cpp
+++ b/src/common/dns_utils.cpp
@@ -32,9 +32,9 @@
#include <stdlib.h>
#include "include_base_utils.h"
+#include "common/threadpool.h"
#include <random>
#include <boost/thread/mutex.hpp>
-#include <boost/thread/thread.hpp>
#include <boost/algorithm/string/join.hpp>
#include <boost/optional.hpp>
using namespace epee;
@@ -523,16 +523,16 @@ bool load_txt_records_from_dns(std::vector<std::string> &good_records, const std
size_t first_index = dis(gen);
// send all requests in parallel
- std::vector<boost::thread> threads(dns_urls.size());
std::deque<bool> avail(dns_urls.size(), false), valid(dns_urls.size(), false);
+ tools::threadpool& tpool = tools::threadpool::getInstance();
+ tools::threadpool::waiter waiter;
for (size_t n = 0; n < dns_urls.size(); ++n)
{
- threads[n] = boost::thread([n, dns_urls, &records, &avail, &valid](){
+ tpool.submit(&waiter,[n, dns_urls, &records, &avail, &valid](){
records[n] = tools::DNSResolver::instance().get_txt_record(dns_urls[n], avail[n], valid[n]);
});
}
- for (size_t n = 0; n < dns_urls.size(); ++n)
- threads[n].join();
+ waiter.wait(&tpool);
size_t cur_index = first_index;
do
diff --git a/src/common/util.cpp b/src/common/util.cpp
index 80b8a9e81..728efc294 100644
--- a/src/common/util.cpp
+++ b/src/common/util.cpp
@@ -80,6 +80,7 @@ using namespace epee;
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
#include <boost/asio.hpp>
+#include <boost/format.hpp>
#include <openssl/sha.h>
#undef MONERO_DEFAULT_LOG_CATEGORY
@@ -1063,4 +1064,39 @@ std::string get_nix_version_display_string()
strftime(buffer, sizeof(buffer), "%Y-%m-%d %H:%M:%S", &tm);
return std::string(buffer);
}
+
+ std::string get_human_readable_bytes(uint64_t bytes)
+ {
+ // Use 1024 for "kilo", 1024*1024 for "mega" and so on instead of the more modern and standard-conforming
+ // 1000, 1000*1000 and so on, to be consistent with other Monero code that also uses base 2 units
+ struct byte_map
+ {
+ const char* const format;
+ const std::uint64_t bytes;
+ };
+
+ static constexpr const byte_map sizes[] =
+ {
+ {"%.0f B", 1024},
+ {"%.2f KB", 1024 * 1024},
+ {"%.2f MB", std::uint64_t(1024) * 1024 * 1024},
+ {"%.2f GB", std::uint64_t(1024) * 1024 * 1024 * 1024},
+ {"%.2f TB", std::uint64_t(1024) * 1024 * 1024 * 1024 * 1024}
+ };
+
+ struct bytes_less
+ {
+ bool operator()(const byte_map& lhs, const byte_map& rhs) const noexcept
+ {
+ return lhs.bytes < rhs.bytes;
+ }
+ };
+
+ const auto size = std::upper_bound(
+ std::begin(sizes), std::end(sizes) - 1, byte_map{"", bytes}, bytes_less{}
+ );
+ const std::uint64_t divisor = size->bytes / 1024;
+ return (boost::format(size->format) % (double(bytes) / divisor)).str();
+ }
+
}
diff --git a/src/common/util.h b/src/common/util.h
index ef2305bf4..77a5a9af6 100644
--- a/src/common/util.h
+++ b/src/common/util.h
@@ -244,4 +244,6 @@ namespace tools
void closefrom(int fd);
std::string get_human_readable_timestamp(uint64_t ts);
+
+ std::string get_human_readable_bytes(uint64_t bytes);
}
diff --git a/src/cryptonote_basic/cryptonote_boost_serialization.h b/src/cryptonote_basic/cryptonote_boost_serialization.h
index 1840b6d2b..3dd98f0c6 100644
--- a/src/cryptonote_basic/cryptonote_boost_serialization.h
+++ b/src/cryptonote_basic/cryptonote_boost_serialization.h
@@ -40,6 +40,7 @@
#include <boost/archive/portable_binary_iarchive.hpp>
#include <boost/archive/portable_binary_oarchive.hpp>
#include "cryptonote_basic.h"
+#include "difficulty.h"
#include "common/unordered_containers_boost_serialization.h"
#include "crypto/crypto.h"
#include "ringct/rctTypes.h"
@@ -346,6 +347,34 @@ namespace boost
a & x.range_proof_type;
a & x.bp_version;
}
+
+ template <class Archive>
+ inline void serialize(Archive &a, cryptonote::difficulty_type &x, const boost::serialization::version_type ver)
+ {
+ if (Archive::is_loading::value)
+ {
+ // load high part
+ uint64_t v = 0;
+ a & v;
+ x = v;
+ // load low part
+ x = x << 64;
+ a & v;
+ x += v;
+ }
+ else
+ {
+ // store high part
+ cryptonote::difficulty_type x_ = x >> 64;
+ uint64_t v = x_.convert_to<uint64_t>();
+ a & v;
+ // store low part
+ x_ = x << 64 >> 64;
+ v = x_.convert_to<uint64_t>();
+ a & v;
+ }
+ }
+
}
}
diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp
index 89b748a83..5162e53e6 100644
--- a/src/cryptonote_basic/difficulty.cpp
+++ b/src/cryptonote_basic/difficulty.cpp
@@ -102,7 +102,7 @@ namespace cryptonote {
return a + b < a || (c && a + b == (uint64_t) -1);
}
- bool check_hash(const crypto::hash &hash, difficulty_type difficulty) {
+ bool check_hash_64(const crypto::hash &hash, uint64_t difficulty) {
uint64_t low, high, top, cur;
// First check the highest word, this will most likely fail for a random hash.
mul(swap64le(((const uint64_t *) &hash)[3]), difficulty, top, high);
@@ -119,7 +119,7 @@ namespace cryptonote {
return !carry;
}
- difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {
+ uint64_t next_difficulty_64(std::vector<std::uint64_t> timestamps, std::vector<uint64_t> cumulative_difficulties, size_t target_seconds) {
if(timestamps.size() > DIFFICULTY_WINDOW)
{
@@ -150,7 +150,7 @@ namespace cryptonote {
if (time_span == 0) {
time_span = 1;
}
- difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin];
+ uint64_t total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin];
assert(total_work > 0);
uint64_t low, high;
mul(total_work, target_seconds, low, high);
@@ -162,4 +162,81 @@ namespace cryptonote {
return (low + time_span - 1) / time_span;
}
+#if defined(_MSC_VER)
+#ifdef max
+#undef max
+#endif
+#endif
+
+ const difficulty_type max64bit(std::numeric_limits<std::uint64_t>::max());
+ const boost::multiprecision::uint256_t max128bit(std::numeric_limits<boost::multiprecision::uint128_t>::max());
+ const boost::multiprecision::uint512_t max256bit(std::numeric_limits<boost::multiprecision::uint256_t>::max());
+
+#define FORCE_FULL_128_BITS
+
+ bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty) {
+#ifndef FORCE_FULL_128_BITS
+ // fast check
+ if (difficulty >= max64bit && ((const uint64_t *) &hash)[3] > 0)
+ return false;
+#endif
+ // usual slow check
+ boost::multiprecision::uint512_t hashVal = 0;
+#ifdef FORCE_FULL_128_BITS
+ for(int i = 0; i < 4; i++) { // highest word is zero
+#else
+ for(int i = 1; i < 4; i++) { // highest word is zero
+#endif
+ hashVal <<= 64;
+ hashVal |= swap64le(((const uint64_t *) &hash)[3 - i]);
+ }
+ return hashVal * difficulty <= max256bit;
+ }
+
+ bool check_hash(const crypto::hash &hash, difficulty_type difficulty) {
+ if (difficulty <= max64bit) // if can convert to small difficulty - do it
+ return check_hash_64(hash, difficulty.convert_to<std::uint64_t>());
+ else
+ return check_hash_128(hash, difficulty);
+ }
+
+ difficulty_type next_difficulty(std::vector<uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds) {
+ //cutoff DIFFICULTY_LAG
+ if(timestamps.size() > DIFFICULTY_WINDOW)
+ {
+ timestamps.resize(DIFFICULTY_WINDOW);
+ cumulative_difficulties.resize(DIFFICULTY_WINDOW);
+ }
+
+
+ size_t length = timestamps.size();
+ assert(length == cumulative_difficulties.size());
+ if (length <= 1) {
+ return 1;
+ }
+ static_assert(DIFFICULTY_WINDOW >= 2, "Window is too small");
+ assert(length <= DIFFICULTY_WINDOW);
+ sort(timestamps.begin(), timestamps.end());
+ size_t cut_begin, cut_end;
+ static_assert(2 * DIFFICULTY_CUT <= DIFFICULTY_WINDOW - 2, "Cut length is too large");
+ if (length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) {
+ cut_begin = 0;
+ cut_end = length;
+ } else {
+ cut_begin = (length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT) + 1) / 2;
+ cut_end = cut_begin + (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT);
+ }
+ assert(/*cut_begin >= 0 &&*/ cut_begin + 2 <= cut_end && cut_end <= length);
+ uint64_t time_span = timestamps[cut_end - 1] - timestamps[cut_begin];
+ if (time_span == 0) {
+ time_span = 1;
+ }
+ difficulty_type total_work = cumulative_difficulties[cut_end - 1] - cumulative_difficulties[cut_begin];
+ assert(total_work > 0);
+ boost::multiprecision::uint256_t res = (boost::multiprecision::uint256_t(total_work) * target_seconds + time_span - 1) / time_span;
+ if(res > max128bit)
+ return 0; // to behave like previous implementation, may be better return max128bit?
+ return res.convert_to<difficulty_type>();
+ }
+
}
diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h
index 8da355b22..f7a9376fb 100644
--- a/src/cryptonote_basic/difficulty.h
+++ b/src/cryptonote_basic/difficulty.h
@@ -32,12 +32,13 @@
#include <cstdint>
#include <vector>
+#include <boost/multiprecision/cpp_int.hpp>
#include "crypto/hash.h"
namespace cryptonote
{
- typedef std::uint64_t difficulty_type;
+ typedef boost::multiprecision::uint128_t difficulty_type;
/**
* @brief checks if a hash fits the given difficulty
@@ -51,6 +52,10 @@ namespace cryptonote
*
* @return true if valid, else false
*/
+ bool check_hash_64(const crypto::hash &hash, uint64_t difficulty);
+ uint64_t next_difficulty_64(std::vector<std::uint64_t> timestamps, std::vector<uint64_t> cumulative_difficulties, size_t target_seconds);
+
+ bool check_hash_128(const crypto::hash &hash, difficulty_type difficulty);
bool check_hash(const crypto::hash &hash, difficulty_type difficulty);
difficulty_type next_difficulty(std::vector<std::uint64_t> timestamps, std::vector<difficulty_type> cumulative_difficulties, size_t target_seconds);
}
diff --git a/src/cryptonote_basic/miner.cpp b/src/cryptonote_basic/miner.cpp
index 6628c8448..4e2edc20f 100644
--- a/src/cryptonote_basic/miner.cpp
+++ b/src/cryptonote_basic/miner.cpp
@@ -435,14 +435,15 @@ namespace cryptonote
{
MTRACE("Miner has received stop signal");
- if (!is_mining())
+ CRITICAL_REGION_LOCAL(m_threads_lock);
+ bool mining = !m_threads.empty();
+ if (!mining)
{
MTRACE("Not mining - nothing to stop" );
return true;
}
send_stop_signal();
- CRITICAL_REGION_LOCAL(m_threads_lock);
// In case background mining was active and the miner threads are waiting
// on the background miner to signal start.
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index f5bd9bbb5..83d3044f8 100644
--- a/src/cryptonote_core/blockchain.cpp
+++ b/src/cryptonote_core/blockchain.cpp
@@ -178,6 +178,7 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool) :
m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_sync_on_blocks(true), m_db_sync_threshold(1), m_db_sync_mode(db_async), m_db_default_sync(false), m_fast_sync(true), m_show_time_stats(false), m_sync_counter(0), m_bytes_to_sync(0), m_cancel(false),
m_long_term_block_weights_window(CRYPTONOTE_LONG_TERM_BLOCK_WEIGHT_WINDOW_SIZE),
m_long_term_effective_median_block_weight(0),
+ m_long_term_block_weights_cache_tip_hash(crypto::null_hash),
m_difficulty_for_next_block_top_hash(crypto::null_hash),
m_difficulty_for_next_block(1),
m_btc_valid(false)
@@ -645,6 +646,8 @@ block Blockchain::pop_block_from_blockchain()
block popped_block;
std::vector<transaction> popped_txs;
+ CHECK_AND_ASSERT_THROW_MES(m_db->height() > 1, "Cannot pop the genesis block");
+
try
{
m_db->pop_block(popped_block, popped_txs);
@@ -1279,7 +1282,50 @@ void Blockchain::get_long_term_block_weights(std::vector<uint64_t>& weights, uin
LOG_PRINT_L3("Blockchain::" << __func__);
CRITICAL_REGION_LOCAL(m_blockchain_lock);
+ PERF_TIMER(get_long_term_block_weights);
+
+ if (count == 0)
+ return;
+
+ bool cached = false;
+ uint64_t blockchain_height = m_db->height();
+ uint64_t tip_height = start_height + count - 1;
+ crypto::hash tip_hash = crypto::null_hash;
+ if (tip_height < blockchain_height && count == m_long_term_block_weights_cache.size())
+ {
+ tip_hash = m_db->get_block_hash_from_height(tip_height);
+ cached = tip_hash == m_long_term_block_weights_cache_tip_hash;
+ }
+
+ if (cached)
+ {
+ MTRACE("requesting " << count << " from " << start_height << ", cached");
+ weights = m_long_term_block_weights_cache;
+ return;
+ }
+
+ // in the vast majority of uncached cases, most is still cached,
+ // as we just move the window one block up:
+ if (tip_height > 0 && count == m_long_term_block_weights_cache.size() && tip_height < blockchain_height)
+ {
+ crypto::hash old_tip_hash = m_db->get_block_hash_from_height(tip_height - 1);
+ if (old_tip_hash == m_long_term_block_weights_cache_tip_hash)
+ {
+ weights = m_long_term_block_weights_cache;
+ for (size_t i = 1; i < weights.size(); ++i)
+ weights[i - 1] = weights[i];
+ MTRACE("requesting " << count << " from " << start_height << ", incremental");
+ weights.back() = m_db->get_block_long_term_weight(tip_height);
+ m_long_term_block_weights_cache = weights;
+ m_long_term_block_weights_cache_tip_hash = tip_hash;
+ return;
+ }
+ }
+
+ MTRACE("requesting " << count << " from " << start_height << ", uncached");
weights = m_db->get_long_term_block_weights(start_height, count);
+ m_long_term_block_weights_cache = weights;
+ m_long_term_block_weights_cache_tip_hash = tip_hash;
}
//------------------------------------------------------------------
uint64_t Blockchain::get_current_cumulative_block_weight_limit() const
@@ -1997,7 +2043,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
return true;
}
//------------------------------------------------------------------
-uint64_t Blockchain::block_difficulty(uint64_t i) const
+difficulty_type Blockchain::block_difficulty(uint64_t i) const
{
LOG_PRINT_L3("Blockchain::" << __func__);
// WARNING: this function does not take m_blockchain_lock, and thus should only call read only
@@ -2196,7 +2242,11 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc
bool result = find_blockchain_supplement(qblock_ids, resp.m_block_ids, resp.start_height, resp.total_height);
if (result)
- resp.cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1);
+ {
+ cryptonote::difficulty_type wide_cumulative_difficulty = m_db->get_block_cumulative_difficulty(resp.total_height - 1);
+ resp.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>();
+ resp.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>();
+ }
return result;
}
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 3b8169764..2cd4dc31b 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -653,7 +653,7 @@ namespace cryptonote
*
* @return the difficulty
*/
- uint64_t block_difficulty(uint64_t i) const;
+ difficulty_type block_difficulty(uint64_t i) const;
/**
* @brief gets blocks based on a list of block hashes
@@ -1060,6 +1060,8 @@ namespace cryptonote
uint64_t m_timestamps_and_difficulties_height;
uint64_t m_long_term_block_weights_window;
uint64_t m_long_term_effective_median_block_weight;
+ mutable crypto::hash m_long_term_block_weights_cache_tip_hash;
+ mutable std::vector<uint64_t> m_long_term_block_weights_cache;
epee::critical_section m_difficulty_lock;
crypto::hash m_difficulty_for_next_block_top_hash;
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index 387203cc0..da413bbe2 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -1784,12 +1784,28 @@ namespace cryptonote
return f;
}
//-----------------------------------------------------------------------------------------------
- static double probability(unsigned int blocks, unsigned int expected)
+ static double probability1(unsigned int blocks, unsigned int expected)
{
// https://www.umass.edu/wsp/resources/poisson/#computing
return pow(expected, blocks) / (factorial(blocks) * exp(expected));
}
//-----------------------------------------------------------------------------------------------
+ static double probability(unsigned int blocks, unsigned int expected)
+ {
+ double p = 0.0;
+ if (blocks <= expected)
+ {
+ for (unsigned int b = 0; b <= blocks; ++b)
+ p += probability1(b, expected);
+ }
+ else if (blocks > expected)
+ {
+ for (unsigned int b = blocks; b <= expected * 3 /* close enough */; ++b)
+ p += probability1(b, expected);
+ }
+ return p;
+ }
+ //-----------------------------------------------------------------------------------------------
bool core::check_block_rate()
{
if (m_offline || m_target_blockchain_height > get_current_blockchain_height())
diff --git a/src/cryptonote_protocol/cryptonote_protocol_defs.h b/src/cryptonote_protocol/cryptonote_protocol_defs.h
index d582e3e9c..3083a5b4c 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_defs.h
+++ b/src/cryptonote_protocol/cryptonote_protocol_defs.h
@@ -34,6 +34,7 @@
#include "serialization/keyvalue_serialization.h"
#include "cryptonote_basic/cryptonote_basic.h"
#include "cryptonote_basic/blobdatatype.h"
+
namespace cryptonote
{
@@ -208,6 +209,7 @@ namespace cryptonote
{
uint64_t current_height;
uint64_t cumulative_difficulty;
+ uint64_t cumulative_difficulty_top64;
crypto::hash top_id;
uint8_t top_version;
uint32_t pruning_seed;
@@ -215,6 +217,7 @@ namespace cryptonote
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(current_height)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(cumulative_difficulty_top64)
KV_SERIALIZE_VAL_POD_AS_BLOB(top_id)
KV_SERIALIZE_OPT(top_version, (uint8_t)0)
KV_SERIALIZE_OPT(pruning_seed, (uint32_t)0)
@@ -245,12 +248,14 @@ namespace cryptonote
uint64_t start_height;
uint64_t total_height;
uint64_t cumulative_difficulty;
+ uint64_t cumulative_difficulty_top64;
std::vector<crypto::hash> m_block_ids;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(start_height)
KV_SERIALIZE(total_height)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(cumulative_difficulty_top64)
KV_SERIALIZE_CONTAINER_POD_AS_BLOB(m_block_ids)
END_KV_SERIALIZE_MAP()
};
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index b33867e8b..32f0afceb 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -398,7 +398,9 @@ namespace cryptonote
{
m_core.get_blockchain_top(hshd.current_height, hshd.top_id);
hshd.top_version = m_core.get_ideal_hard_fork_version(hshd.current_height);
- hshd.cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height);
+ difficulty_type wide_cumulative_difficulty = m_core.get_block_cumulative_difficulty(hshd.current_height);
+ hshd.cumulative_difficulty = (wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>();
+ hshd.cumulative_difficulty_top64 = (wide_cumulative_difficulty >> 64).convert_to<uint64_t>();
hshd.current_height +=1;
hshd.pruning_seed = m_core.get_blockchain_pruning_seed();
return true;
diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp
index b324ab99d..17b945c9a 100644
--- a/src/daemon/command_parser_executor.cpp
+++ b/src/daemon/command_parser_executor.cpp
@@ -127,6 +127,13 @@ bool t_command_parser_executor::print_connections(const std::vector<std::string>
return m_executor.print_connections();
}
+bool t_command_parser_executor::print_net_stats(const std::vector<std::string>& args)
+{
+ if (!args.empty()) return false;
+
+ return m_executor.print_net_stats();
+}
+
bool t_command_parser_executor::print_blockchain_info(const std::vector<std::string>& args)
{
if(!args.size())
diff --git a/src/daemon/command_parser_executor.h b/src/daemon/command_parser_executor.h
index bec6e4522..098018642 100644
--- a/src/daemon/command_parser_executor.h
+++ b/src/daemon/command_parser_executor.h
@@ -148,6 +148,8 @@ public:
bool prune_blockchain(const std::vector<std::string>& args);
bool check_blockchain_pruning(const std::vector<std::string>& args);
+
+ bool print_net_stats(const std::vector<std::string>& args);
};
} // namespace daemonize
diff --git a/src/daemon/command_server.cpp b/src/daemon/command_server.cpp
index 94e4a8bf1..69ad6ff10 100644
--- a/src/daemon/command_server.cpp
+++ b/src/daemon/command_server.cpp
@@ -78,6 +78,11 @@ t_command_server::t_command_server(
, "Print the current connections."
);
m_command_lookup.set_handler(
+ "print_net_stats"
+ , std::bind(&t_command_parser_executor::print_net_stats, &m_parser, p::_1)
+ , "Print network statistics."
+ );
+ m_command_lookup.set_handler(
"print_bc"
, std::bind(&t_command_parser_executor::print_blockchain_info, &m_parser, p::_1)
, "print_bc <begin_height> [<end_height>]"
diff --git a/src/daemon/daemon.cpp b/src/daemon/daemon.cpp
index 3d1d893ea..531c080de 100644
--- a/src/daemon/daemon.cpp
+++ b/src/daemon/daemon.cpp
@@ -115,6 +115,7 @@ t_daemon::t_daemon(t_daemon && other)
{
mp_internals = std::move(other.mp_internals);
other.mp_internals.reset(nullptr);
+ public_rpc_port = other.public_rpc_port;
}
}
@@ -125,6 +126,7 @@ t_daemon & t_daemon::operator=(t_daemon && other)
{
mp_internals = std::move(other.mp_internals);
other.mp_internals.reset(nullptr);
+ public_rpc_port = other.public_rpc_port;
}
return *this;
}
diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp
index 4ee67f571..c9ec5109e 100644
--- a/src/daemon/rpc_command_executor.cpp
+++ b/src/daemon/rpc_command_executor.cpp
@@ -627,6 +627,66 @@ bool t_rpc_command_executor::print_connections() {
return true;
}
+bool t_rpc_command_executor::print_net_stats()
+{
+ cryptonote::COMMAND_RPC_GET_NET_STATS::request net_stats_req;
+ cryptonote::COMMAND_RPC_GET_NET_STATS::response net_stats_res;
+ cryptonote::COMMAND_RPC_GET_LIMIT::request limit_req;
+ cryptonote::COMMAND_RPC_GET_LIMIT::response limit_res;
+
+ std::string fail_message = "Unsuccessful";
+
+ if (m_is_rpc)
+ {
+ if (!m_rpc_client->json_rpc_request(net_stats_req, net_stats_res, "get_net_stats", fail_message.c_str()))
+ {
+ return true;
+ }
+ if (!m_rpc_client->json_rpc_request(limit_req, limit_res, "get_limit", fail_message.c_str()))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (!m_rpc_server->on_get_net_stats(net_stats_req, net_stats_res) || net_stats_res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(fail_message, net_stats_res.status);
+ return true;
+ }
+ if (!m_rpc_server->on_get_limit(limit_req, limit_res) || limit_res.status != CORE_RPC_STATUS_OK)
+ {
+ tools::fail_msg_writer() << make_error(fail_message, limit_res.status);
+ return true;
+ }
+ }
+
+ uint64_t seconds = (uint64_t)time(NULL) - net_stats_res.start_time;
+ uint64_t average = seconds > 0 ? net_stats_res.total_bytes_in / seconds : 0;
+ uint64_t limit = limit_res.limit_down * 1024; // convert to bytes, as limits are always kB/s
+ double percent = (double)average / (double)limit * 100.0;
+ tools::success_msg_writer() << boost::format("Received %u bytes (%s) in %u packets, average %s/s = %.2f%% of the limit of %s/s")
+ % net_stats_res.total_bytes_in
+ % tools::get_human_readable_bytes(net_stats_res.total_bytes_in)
+ % net_stats_res.total_packets_in
+ % tools::get_human_readable_bytes(average)
+ % percent
+ % tools::get_human_readable_bytes(limit);
+
+ average = seconds > 0 ? net_stats_res.total_bytes_out / seconds : 0;
+ limit = limit_res.limit_up * 1024;
+ percent = (double)average / (double)limit * 100.0;
+ tools::success_msg_writer() << boost::format("Sent %u bytes (%s) in %u packets, average %s/s = %.2f%% of the limit of %s/s")
+ % net_stats_res.total_bytes_out
+ % tools::get_human_readable_bytes(net_stats_res.total_bytes_out)
+ % net_stats_res.total_packets_out
+ % tools::get_human_readable_bytes(average)
+ % percent
+ % tools::get_human_readable_bytes(limit);
+
+ return true;
+}
+
bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, uint64_t end_block_index) {
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::request req;
cryptonote::COMMAND_RPC_GET_BLOCK_HEADERS_RANGE::response res;
diff --git a/src/daemon/rpc_command_executor.h b/src/daemon/rpc_command_executor.h
index 423132b79..3c2686b3f 100644
--- a/src/daemon/rpc_command_executor.h
+++ b/src/daemon/rpc_command_executor.h
@@ -160,6 +160,8 @@ public:
bool prune_blockchain();
bool check_blockchain_pruning();
+
+ bool print_net_stats();
};
} // namespace daemonize
diff --git a/src/mnemonics/electrum-words.cpp b/src/mnemonics/electrum-words.cpp
index 48c9ab1ba..2dd40cc9a 100644
--- a/src/mnemonics/electrum-words.cpp
+++ b/src/mnemonics/electrum-words.cpp
@@ -340,9 +340,7 @@ namespace crypto
const size_t expected = len * 3 / 32;
if (seed.size() == expected/2)
{
- dst += ' '; // if electrum 12-word seed, duplicate
- dst += dst; // if electrum 12-word seed, duplicate
- dst.pop_back(); // trailing space
+ dst.append(dst.data(), dst.size()); // if electrum 12-word seed, duplicate
}
}
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index ba6e79d3f..565fcf0ea 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -2236,11 +2236,10 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::set_rate_up_limit(const boost::program_options::variables_map& vm, int64_t limit)
{
- this->islimitup=true;
+ this->islimitup=(limit != -1) && (limit != default_limit_up);
if (limit==-1) {
limit=default_limit_up;
- this->islimitup=false;
}
epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_up_limit( limit );
@@ -2251,10 +2250,9 @@ namespace nodetool
template<class t_payload_net_handler>
bool node_server<t_payload_net_handler>::set_rate_down_limit(const boost::program_options::variables_map& vm, int64_t limit)
{
- this->islimitdown=true;
+ this->islimitdown=(limit != -1) && (limit != default_limit_down);
if(limit==-1) {
limit=default_limit_down;
- this->islimitdown=false;
}
epee::net_utils::connection<epee::levin::async_protocol_handler<p2p_connection_context> >::set_rate_down_limit( limit );
MINFO("Set limit-down to " << limit << " kB/s");
diff --git a/src/ringct/rctOps.cpp b/src/ringct/rctOps.cpp
index e39ba16fd..b5499262f 100644
--- a/src/ringct/rctOps.cpp
+++ b/src/ringct/rctOps.cpp
@@ -408,10 +408,10 @@ namespace rct {
return res;
}
- //Computes aL where L is the curve order
- bool isInMainSubgroup(const key & a) {
+ //Computes lA where l is the curve order
+ bool isInMainSubgroup(const key & A) {
ge_p3 p3;
- return toPointCheckOrder(&p3, a.bytes);
+ return toPointCheckOrder(&p3, A.bytes);
}
//Curve addition / subtractions
diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp
index 56b0361a7..f5f1a2f9a 100644
--- a/src/rpc/core_rpc_server.cpp
+++ b/src/rpc/core_rpc_server.cpp
@@ -70,6 +70,13 @@ namespace
{
return (value + quantum - 1) / quantum * quantum;
}
+
+ void store_difficulty(cryptonote::difficulty_type difficulty, uint64_t &sdiff, std::string &swdiff, uint64_t &stop64)
+ {
+ sdiff = (difficulty << 64 >> 64).convert_to<uint64_t>();
+ swdiff = difficulty.convert_to<std::string>();
+ stop64 = (difficulty >> 64).convert_to<uint64_t>();
+ }
}
namespace cryptonote
@@ -219,7 +226,7 @@ namespace cryptonote
++res.height; // turn top block height into blockchain height
res.top_block_hash = string_tools::pod_to_hex(top_hash);
res.target_height = m_core.get_target_blockchain_height();
- res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block();
+ store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(), res.difficulty, res.wide_difficulty, res.difficulty_top64);
res.target = m_core.get_blockchain_storage().get_difficulty_target();
res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase
res.tx_pool_size = m_core.get_pool_transactions_count();
@@ -236,7 +243,8 @@ namespace cryptonote
res.testnet = net_type == TESTNET;
res.stagenet = net_type == STAGENET;
res.nettype = net_type == MAINNET ? "mainnet" : net_type == TESTNET ? "testnet" : net_type == STAGENET ? "stagenet" : "fakechain";
- res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
+ store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1),
+ res.cumulative_difficulty, res.wide_cumulative_difficulty, res.cumulative_difficulty_top64);
res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit();
res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median();
res.status = CORE_RPC_STATUS_OK;
@@ -260,6 +268,23 @@ namespace cryptonote
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool core_rpc_server::on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx)
+ {
+ PERF_TIMER(on_get_net_stats);
+ // No bootstrap daemon check: Only ever get stats about local server
+ res.start_time = (uint64_t)m_core.get_start_time();
+ {
+ CRITICAL_REGION_LOCAL(epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_in);
+ epee::net_utils::network_throttle_manager::get_global_throttle_in().get_stats(res.total_packets_in, res.total_bytes_in);
+ }
+ {
+ CRITICAL_REGION_LOCAL(epee::net_utils::network_throttle_manager::m_lock_get_global_throttle_out);
+ epee::net_utils::network_throttle_manager::get_global_throttle_out().get_stats(res.total_packets_out, res.total_bytes_out);
+ }
+ res.status = CORE_RPC_STATUS_OK;
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
class pruned_transaction {
transaction& tx;
public:
@@ -1196,13 +1221,15 @@ namespace cryptonote
block b;
cryptonote::blobdata blob_reserve;
blob_reserve.resize(req.reserve_size, 0);
- if(!m_core.get_block_template(b, info.address, res.difficulty, res.height, res.expected_reward, blob_reserve))
+ cryptonote::difficulty_type wdiff;
+ if(!m_core.get_block_template(b, info.address, wdiff, res.height, res.expected_reward, blob_reserve))
{
error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR;
error_resp.message = "Internal error: failed to create block template";
LOG_ERROR("Failed to create block template");
return false;
}
+ store_difficulty(wdiff, res.difficulty, res.wide_difficulty, res.difficulty_top64);
blobdata block_blob = t_serializable_object_to_blob(b);
crypto::public_key tx_pub_key = cryptonote::get_tx_pub_key_from_extra(b.miner_tx);
if(tx_pub_key == crypto::null_pkey)
@@ -1375,13 +1402,16 @@ namespace cryptonote
response.height = height;
response.depth = m_core.get_current_blockchain_height() - height - 1;
response.hash = string_tools::pod_to_hex(hash);
- response.difficulty = m_core.get_blockchain_storage().block_difficulty(height);
- response.cumulative_difficulty = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height);
+ store_difficulty(m_core.get_blockchain_storage().block_difficulty(height),
+ response.difficulty, response.wide_difficulty, response.difficulty_top64);
+ store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(height),
+ response.cumulative_difficulty, response.wide_cumulative_difficulty, response.cumulative_difficulty_top64);
response.reward = get_block_reward(blk);
response.block_size = response.block_weight = m_core.get_blockchain_storage().get_db().get_block_weight(height);
response.num_txes = blk.tx_hashes.size();
response.pow_hash = fill_pow_hash ? string_tools::pod_to_hex(get_block_longhash(blk, height)) : "";
response.long_term_weight = m_core.get_blockchain_storage().get_db().get_block_long_term_weight(height);
+ response.miner_tx_hash = string_tools::pod_to_hex(cryptonote::get_transaction_hash(blk.miner_tx));
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
@@ -1707,7 +1737,8 @@ namespace cryptonote
++res.height; // turn top block height into blockchain height
res.top_block_hash = string_tools::pod_to_hex(top_hash);
res.target_height = m_core.get_target_blockchain_height();
- res.difficulty = m_core.get_blockchain_storage().get_difficulty_for_next_block();
+ store_difficulty(m_core.get_blockchain_storage().get_difficulty_for_next_block(),
+ res.difficulty, res.wide_difficulty, res.difficulty_top64);
res.target = m_core.get_blockchain_storage().get_current_hard_fork_version() < 2 ? DIFFICULTY_TARGET_V1 : DIFFICULTY_TARGET_V2;
res.tx_count = m_core.get_blockchain_storage().get_total_transactions() - res.height; //without coinbase
res.tx_pool_size = m_core.get_pool_transactions_count();
@@ -1725,7 +1756,8 @@ namespace cryptonote
res.stagenet = net_type == STAGENET;
res.nettype = net_type == MAINNET ? "mainnet" : net_type == TESTNET ? "testnet" : net_type == STAGENET ? "stagenet" : "fakechain";
- res.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1);
+ store_difficulty(m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.height - 1),
+ res.cumulative_difficulty, res.wide_cumulative_difficulty, res.cumulative_difficulty_top64);
res.block_size_limit = res.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit();
res.block_size_median = res.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median();
res.status = CORE_RPC_STATUS_OK;
@@ -1741,7 +1773,9 @@ namespace cryptonote
boost::shared_lock<boost::shared_mutex> lock(m_bootstrap_daemon_mutex);
res.was_bootstrap_ever_used = m_was_bootstrap_ever_used;
}
- res.database_size = restricted ? 0 : m_core.get_blockchain_storage().get_db().get_database_size();
+ res.database_size = m_core.get_blockchain_storage().get_db().get_database_size();
+ if (restricted)
+ res.database_size = round_up(res.database_size, 5ull * 1024 * 1024 * 1024);
res.update_available = restricted ? false : m_core.is_update_available();
res.version = restricted ? "" : MONERO_VERSION;
return true;
@@ -1947,7 +1981,9 @@ namespace cryptonote
std::list<std::pair<Blockchain::block_extended_info, std::vector<crypto::hash>>> chains = m_core.get_blockchain_storage().get_alternative_chains();
for (const auto &i: chains)
{
- res.chains.push_back(COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info{epee::string_tools::pod_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), i.first.cumulative_difficulty, {}, std::string()});
+ difficulty_type wdiff = i.first.cumulative_difficulty;
+ res.chains.push_back(COMMAND_RPC_GET_ALTERNATE_CHAINS::chain_info{epee::string_tools::pod_to_hex(get_block_hash(i.first.bl)), i.first.height, i.second.size(), 0, "", 0, {}, std::string()});
+ store_difficulty(wdiff, res.chains.back().difficulty, res.chains.back().wide_difficulty, res.chains.back().difficulty_top64);
res.chains.back().block_hashes.reserve(i.second.size());
for (const crypto::hash &block_id: i.second)
res.chains.back().block_hashes.push_back(epee::string_tools::pod_to_hex(block_id));
diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h
index fe066b31b..8f5d83f1b 100644
--- a/src/rpc/core_rpc_server.h
+++ b/src/rpc/core_rpc_server.h
@@ -115,6 +115,7 @@ namespace cryptonote
MAP_URI_AUTO_JON2_IF("/stop_daemon", on_stop_daemon, COMMAND_RPC_STOP_DAEMON, !m_restricted)
MAP_URI_AUTO_JON2("/get_info", on_get_info, COMMAND_RPC_GET_INFO)
MAP_URI_AUTO_JON2("/getinfo", on_get_info, COMMAND_RPC_GET_INFO)
+ MAP_URI_AUTO_JON2_IF("/get_net_stats", on_get_net_stats, COMMAND_RPC_GET_NET_STATS, !m_restricted)
MAP_URI_AUTO_JON2("/get_limit", on_get_limit, COMMAND_RPC_GET_LIMIT)
MAP_URI_AUTO_JON2_IF("/set_limit", on_set_limit, COMMAND_RPC_SET_LIMIT, !m_restricted)
MAP_URI_AUTO_JON2_IF("/out_peers", on_out_peers, COMMAND_RPC_OUT_PEERS, !m_restricted)
@@ -179,6 +180,7 @@ namespace cryptonote
bool on_get_outs_bin(const COMMAND_RPC_GET_OUTPUTS_BIN::request& req, COMMAND_RPC_GET_OUTPUTS_BIN::response& res, const connection_context *ctx = NULL);
bool on_get_outs(const COMMAND_RPC_GET_OUTPUTS::request& req, COMMAND_RPC_GET_OUTPUTS::response& res, const connection_context *ctx = NULL);
bool on_get_info(const COMMAND_RPC_GET_INFO::request& req, COMMAND_RPC_GET_INFO::response& res, const connection_context *ctx = NULL);
+ bool on_get_net_stats(const COMMAND_RPC_GET_NET_STATS::request& req, COMMAND_RPC_GET_NET_STATS::response& res, const connection_context *ctx = NULL);
bool on_save_bc(const COMMAND_RPC_SAVE_BC::request& req, COMMAND_RPC_SAVE_BC::response& res, const connection_context *ctx = NULL);
bool on_get_peer_list(const COMMAND_RPC_GET_PEER_LIST::request& req, COMMAND_RPC_GET_PEER_LIST::response& res, const connection_context *ctx = NULL);
bool on_set_log_hash_rate(const COMMAND_RPC_SET_LOG_HASH_RATE::request& req, COMMAND_RPC_SET_LOG_HASH_RATE::response& res, const connection_context *ctx = NULL);
diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h
index f65c7c8dd..1f14267f6 100644
--- a/src/rpc/core_rpc_server_commands_defs.h
+++ b/src/rpc/core_rpc_server_commands_defs.h
@@ -84,7 +84,7 @@ namespace cryptonote
// advance which version they will stop working with
// Don't go over 32767 for any of these
#define CORE_RPC_VERSION_MAJOR 2
-#define CORE_RPC_VERSION_MINOR 4
+#define CORE_RPC_VERSION_MINOR 5
#define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor))
#define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR)
@@ -252,223 +252,6 @@ namespace cryptonote
};
//-----------------------------------------------
- struct COMMAND_RPC_GET_ADDRESS_TXS
- {
- struct request_t
- {
- std::string address;
- std::string view_key;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(address)
- KV_SERIALIZE(view_key)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<request_t> request;
-
- struct spent_output {
- uint64_t amount;
- std::string key_image;
- std::string tx_pub_key;
- uint64_t out_index;
- uint32_t mixin;
-
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(amount)
- KV_SERIALIZE(key_image)
- KV_SERIALIZE(tx_pub_key)
- KV_SERIALIZE(out_index)
- KV_SERIALIZE(mixin)
- END_KV_SERIALIZE_MAP()
- };
-
- struct transaction
- {
- uint64_t id;
- std::string hash;
- uint64_t timestamp;
- uint64_t total_received;
- uint64_t total_sent;
- uint64_t unlock_time;
- uint64_t height;
- std::list<spent_output> spent_outputs;
- std::string payment_id;
- bool coinbase;
- bool mempool;
- uint32_t mixin;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(id)
- KV_SERIALIZE(hash)
- KV_SERIALIZE(timestamp)
- KV_SERIALIZE(total_received)
- KV_SERIALIZE(total_sent)
- KV_SERIALIZE(unlock_time)
- KV_SERIALIZE(height)
- KV_SERIALIZE(spent_outputs)
- KV_SERIALIZE(payment_id)
- KV_SERIALIZE(coinbase)
- KV_SERIALIZE(mempool)
- KV_SERIALIZE(mixin)
- END_KV_SERIALIZE_MAP()
- };
-
-
- struct response_t
- {
- //std::list<std::string> txs_as_json;
- uint64_t total_received;
- uint64_t total_received_unlocked = 0; // OpenMonero only
- uint64_t scanned_height;
- std::vector<transaction> transactions;
- uint64_t blockchain_height;
- uint64_t scanned_block_height;
- std::string status;
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(total_received)
- KV_SERIALIZE(total_received_unlocked)
- KV_SERIALIZE(scanned_height)
- KV_SERIALIZE(transactions)
- KV_SERIALIZE(blockchain_height)
- KV_SERIALIZE(scanned_block_height)
- KV_SERIALIZE(status)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<response_t> response;
- };
-
- //-----------------------------------------------
- struct COMMAND_RPC_GET_ADDRESS_INFO
- {
- struct request_t
- {
- std::string address;
- std::string view_key;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(address)
- KV_SERIALIZE(view_key)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<request_t> request;
-
- struct spent_output
- {
- uint64_t amount;
- std::string key_image;
- std::string tx_pub_key;
- uint64_t out_index;
- uint32_t mixin;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(amount)
- KV_SERIALIZE(key_image)
- KV_SERIALIZE(tx_pub_key)
- KV_SERIALIZE(out_index)
- KV_SERIALIZE(mixin)
- END_KV_SERIALIZE_MAP()
- };
-
- struct response_t
- {
- uint64_t locked_funds;
- uint64_t total_received;
- uint64_t total_sent;
- uint64_t scanned_height;
- uint64_t scanned_block_height;
- uint64_t start_height;
- uint64_t transaction_height;
- uint64_t blockchain_height;
- std::list<spent_output> spent_outputs;
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(locked_funds)
- KV_SERIALIZE(total_received)
- KV_SERIALIZE(total_sent)
- KV_SERIALIZE(scanned_height)
- KV_SERIALIZE(scanned_block_height)
- KV_SERIALIZE(start_height)
- KV_SERIALIZE(transaction_height)
- KV_SERIALIZE(blockchain_height)
- KV_SERIALIZE(spent_outputs)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<response_t> response;
- };
-
- //-----------------------------------------------
- struct COMMAND_RPC_GET_UNSPENT_OUTS
- {
- struct request_t
- {
- std::string amount;
- std::string address;
- std::string view_key;
- // OpenMonero specific
- uint64_t mixin;
- bool use_dust;
- std::string dust_threshold;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(amount)
- KV_SERIALIZE(address)
- KV_SERIALIZE(view_key)
- KV_SERIALIZE(mixin)
- KV_SERIALIZE(use_dust)
- KV_SERIALIZE(dust_threshold)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<request_t> request;
-
-
- struct output {
- uint64_t amount;
- std::string public_key;
- uint64_t index;
- uint64_t global_index;
- std::string rct;
- std::string tx_hash;
- std::string tx_pub_key;
- std::string tx_prefix_hash;
- std::vector<std::string> spend_key_images;
- uint64_t timestamp;
- uint64_t height;
-
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(amount)
- KV_SERIALIZE(public_key)
- KV_SERIALIZE(index)
- KV_SERIALIZE(global_index)
- KV_SERIALIZE(rct)
- KV_SERIALIZE(tx_hash)
- KV_SERIALIZE(tx_pub_key)
- KV_SERIALIZE(tx_prefix_hash)
- KV_SERIALIZE(spend_key_images)
- KV_SERIALIZE(timestamp)
- KV_SERIALIZE(height)
- END_KV_SERIALIZE_MAP()
- };
-
- struct response_t
- {
- uint64_t amount;
- std::list<output> outputs;
- uint64_t per_kb_fee;
- std::string status;
- std::string reason;
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(amount)
- KV_SERIALIZE(outputs)
- KV_SERIALIZE(per_kb_fee)
- KV_SERIALIZE(status)
- KV_SERIALIZE(reason)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<response_t> response;
- };
-
- //-----------------------------------------------
struct COMMAND_RPC_GET_RANDOM_OUTS
{
struct request_t
@@ -548,72 +331,6 @@ namespace cryptonote
typedef epee::misc_utils::struct_init<response_t> response;
};
//-----------------------------------------------
- struct COMMAND_RPC_LOGIN
- {
- struct request_t
- {
- std::string address;
- std::string view_key;
- bool create_account;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(address)
- KV_SERIALIZE(view_key)
- KV_SERIALIZE(create_account)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<request_t> request;
-
- struct response_t
- {
- std::string status;
- std::string reason;
- bool new_address;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(status)
- KV_SERIALIZE(reason)
- KV_SERIALIZE(new_address)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<response_t> response;
- };
- //-----------------------------------------------
- struct COMMAND_RPC_IMPORT_WALLET_REQUEST
- {
- struct request_t
- {
- std::string address;
- std::string view_key;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(address)
- KV_SERIALIZE(view_key)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<request_t> request;
-
- struct response_t
- {
- std::string payment_id;
- uint64_t import_fee;
- bool new_request;
- bool request_fulfilled;
- std::string payment_address;
- std::string status;
-
- BEGIN_KV_SERIALIZE_MAP()
- KV_SERIALIZE(payment_id)
- KV_SERIALIZE(import_fee)
- KV_SERIALIZE(new_request)
- KV_SERIALIZE(request_fulfilled)
- KV_SERIALIZE(payment_address)
- KV_SERIALIZE(status)
- END_KV_SERIALIZE_MAP()
- };
- typedef epee::misc_utils::struct_init<response_t> response;
- };
- //-----------------------------------------------
struct COMMAND_RPC_GET_TRANSACTIONS
{
struct request_t
@@ -943,6 +660,8 @@ namespace cryptonote
uint64_t height;
uint64_t target_height;
uint64_t difficulty;
+ std::string wide_difficulty;
+ uint64_t difficulty_top64;
uint64_t target;
uint64_t tx_count;
uint64_t tx_pool_size;
@@ -958,6 +677,8 @@ namespace cryptonote
std::string nettype;
std::string top_block_hash;
uint64_t cumulative_difficulty;
+ std::string wide_cumulative_difficulty;
+ uint64_t cumulative_difficulty_top64;
uint64_t block_size_limit;
uint64_t block_weight_limit;
uint64_t block_size_median;
@@ -978,6 +699,8 @@ namespace cryptonote
KV_SERIALIZE(height)
KV_SERIALIZE(target_height)
KV_SERIALIZE(difficulty)
+ KV_SERIALIZE(wide_difficulty)
+ KV_SERIALIZE(difficulty_top64)
KV_SERIALIZE(target)
KV_SERIALIZE(tx_count)
KV_SERIALIZE(tx_pool_size)
@@ -993,6 +716,8 @@ namespace cryptonote
KV_SERIALIZE(nettype)
KV_SERIALIZE(top_block_hash)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(wide_cumulative_difficulty)
+ KV_SERIALIZE(cumulative_difficulty_top64)
KV_SERIALIZE(block_size_limit)
KV_SERIALIZE_OPT(block_weight_limit, (uint64_t)0)
KV_SERIALIZE(block_size_median)
@@ -1014,6 +739,39 @@ namespace cryptonote
//-----------------------------------------------
+ struct COMMAND_RPC_GET_NET_STATS
+ {
+ struct request_t
+ {
+
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+
+ struct response_t
+ {
+ std::string status;
+ uint64_t start_time;
+ uint64_t total_packets_in;
+ uint64_t total_bytes_in;
+ uint64_t total_packets_out;
+ uint64_t total_bytes_out;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(start_time)
+ KV_SERIALIZE(total_packets_in)
+ KV_SERIALIZE(total_bytes_in)
+ KV_SERIALIZE(total_packets_out)
+ KV_SERIALIZE(total_bytes_out)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
+ //-----------------------------------------------
struct COMMAND_RPC_STOP_MINING
{
struct request_t
@@ -1149,6 +907,8 @@ namespace cryptonote
struct response_t
{
uint64_t difficulty;
+ std::string wide_difficulty;
+ uint64_t difficulty_top64;
uint64_t height;
uint64_t reserved_offset;
uint64_t expected_reward;
@@ -1160,6 +920,8 @@ namespace cryptonote
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(difficulty)
+ KV_SERIALIZE(wide_difficulty)
+ KV_SERIALIZE(difficulty_top64)
KV_SERIALIZE(height)
KV_SERIALIZE(reserved_offset)
KV_SERIALIZE(expected_reward)
@@ -1226,14 +988,19 @@ namespace cryptonote
uint64_t height;
uint64_t depth;
std::string hash;
- difficulty_type difficulty;
- difficulty_type cumulative_difficulty;
+ uint64_t difficulty;
+ std::string wide_difficulty;
+ uint64_t difficulty_top64;
+ uint64_t cumulative_difficulty;
+ std::string wide_cumulative_difficulty;
+ uint64_t cumulative_difficulty_top64;
uint64_t reward;
uint64_t block_size;
uint64_t block_weight;
uint64_t num_txes;
std::string pow_hash;
uint64_t long_term_weight;
+ std::string miner_tx_hash;
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(major_version)
@@ -1246,13 +1013,18 @@ namespace cryptonote
KV_SERIALIZE(depth)
KV_SERIALIZE(hash)
KV_SERIALIZE(difficulty)
+ KV_SERIALIZE(wide_difficulty)
+ KV_SERIALIZE(difficulty_top64)
KV_SERIALIZE(cumulative_difficulty)
+ KV_SERIALIZE(wide_cumulative_difficulty)
+ KV_SERIALIZE(cumulative_difficulty_top64)
KV_SERIALIZE(reward)
KV_SERIALIZE(block_size)
KV_SERIALIZE_OPT(block_weight, (uint64_t)0)
KV_SERIALIZE(num_txes)
KV_SERIALIZE(pow_hash)
KV_SERIALIZE_OPT(long_term_weight, (uint64_t)0)
+ KV_SERIALIZE(miner_tx_hash)
END_KV_SERIALIZE_MAP()
};
@@ -2248,6 +2020,8 @@ namespace cryptonote
uint64_t height;
uint64_t length;
uint64_t difficulty;
+ std::string wide_difficulty;
+ uint64_t difficulty_top64;
std::vector<std::string> block_hashes;
std::string main_chain_parent_block;
@@ -2256,6 +2030,8 @@ namespace cryptonote
KV_SERIALIZE(height)
KV_SERIALIZE(length)
KV_SERIALIZE(difficulty)
+ KV_SERIALIZE(wide_difficulty)
+ KV_SERIALIZE(difficulty_top64)
KV_SERIALIZE(block_hashes)
KV_SERIALIZE(main_chain_parent_block)
END_KV_SERIALIZE_MAP()
diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp
index bde2339bc..540afe6b9 100644
--- a/src/rpc/daemon_handler.cpp
+++ b/src/rpc/daemon_handler.cpp
@@ -436,7 +436,8 @@ namespace rpc
auto& chain = m_core.get_blockchain_storage();
- res.info.difficulty = chain.get_difficulty_for_next_block();
+ res.info.wide_difficulty = chain.get_difficulty_for_next_block();
+ res.info.difficulty = (res.info.wide_difficulty << 64 >> 64).convert_to<uint64_t>();
res.info.target = chain.get_difficulty_target();
@@ -457,7 +458,8 @@ namespace rpc
res.info.mainnet = m_core.get_nettype() == MAINNET;
res.info.testnet = m_core.get_nettype() == TESTNET;
res.info.stagenet = m_core.get_nettype() == STAGENET;
- res.info.cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1);
+ res.info.wide_cumulative_difficulty = m_core.get_blockchain_storage().get_db().get_block_cumulative_difficulty(res.info.height - 1);
+ res.info.cumulative_difficulty = (res.info.wide_cumulative_difficulty << 64 >> 64).convert_to<uint64_t>();
res.info.block_size_limit = res.info.block_weight_limit = m_core.get_blockchain_storage().get_current_cumulative_block_weight_limit();
res.info.block_size_median = res.info.block_weight_median = m_core.get_blockchain_storage().get_current_cumulative_block_weight_median();
res.info.start_time = (uint64_t)m_core.get_start_time();
@@ -826,7 +828,8 @@ namespace rpc
header.reward += out.amount;
}
- header.difficulty = m_core.get_blockchain_storage().block_difficulty(header.height);
+ header.wide_difficulty = m_core.get_blockchain_storage().block_difficulty(header.height);
+ header.difficulty = (header.wide_difficulty << 64 >> 64).convert_to<uint64_t>();
return true;
}
diff --git a/src/rpc/message_data_structs.h b/src/rpc/message_data_structs.h
index 26c5038f6..2a43811cf 100644
--- a/src/rpc/message_data_structs.h
+++ b/src/rpc/message_data_structs.h
@@ -30,6 +30,7 @@
#include "crypto/hash.h"
#include "cryptonote_basic/cryptonote_basic.h"
+#include "cryptonote_basic/difficulty.h"
#include "ringct/rctSigs.h"
#include "rpc/rpc_handler.h"
@@ -165,6 +166,7 @@ namespace rpc
uint64_t height;
uint64_t depth;
crypto::hash hash;
+ cryptonote::difficulty_type wide_difficulty;
uint64_t difficulty;
uint64_t reward;
};
@@ -173,6 +175,7 @@ namespace rpc
{
uint64_t height;
uint64_t target_height;
+ cryptonote::difficulty_type wide_difficulty;
uint64_t difficulty;
uint64_t target;
uint64_t tx_count;
@@ -187,6 +190,7 @@ namespace rpc
bool stagenet;
std::string nettype;
crypto::hash top_block_hash;
+ cryptonote::difficulty_type wide_cumulative_difficulty;
uint64_t cumulative_difficulty;
uint64_t block_size_limit;
uint64_t block_weight_limit;
diff --git a/src/serialization/difficulty_type.h b/src/serialization/difficulty_type.h
new file mode 100644
index 000000000..e32e24b78
--- /dev/null
+++ b/src/serialization/difficulty_type.h
@@ -0,0 +1,65 @@
+// Copyright (c) 2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "cryptonote_basic/difficulty.h"
+#include "serialization.h"
+
+template<> struct is_basic_type<cryptonote::difficulty_type> { typedef boost::true_type type; };
+
+template <template <bool> class Archive>
+inline bool do_serialize(Archive<false>& ar, cryptonote::difficulty_type &diff)
+{
+ uint64_t hi, lo;
+ ar.serialize_varint(hi);
+ if (!ar.stream().good())
+ return false;
+ ar.serialize_varint(lo);
+ if (!ar.stream().good())
+ return false;
+ diff = hi;
+ diff <<= 64;
+ diff += lo;
+ return true;
+}
+
+template <template <bool> class Archive>
+inline bool do_serialize(Archive<true>& ar, cryptonote::difficulty_type &diff)
+{
+ if (!ar.stream().good())
+ return false;
+ const uint64_t hi = (diff >> 64).convert_to<uint64_t>();
+ const uint64_t lo = (diff << 64 >> 64).convert_to<uint64_t>();
+ ar.serialize_varint(hi);
+ ar.serialize_varint(lo);
+ if (!ar.stream().good())
+ return false;
+ return true;
+}
+
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index ff7a72b7e..25d40e5fb 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -116,7 +116,9 @@ typedef cryptonote::simple_wallet sw;
#define LONG_PAYMENT_ID_SUPPORT_CHECK() \
do { \
if (!m_long_payment_id_support) { \
- fail_msg_writer() << tr("Long payment IDs are obsolete. Use --long-payment-id-support if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \
+ fail_msg_writer() << tr("Warning: Long payment IDs are obsolete."); \
+ fail_msg_writer() << tr("Long payment IDs are not encrypted on the blockchain, and will harm your privacy."); \
+ fail_msg_writer() << tr("Use --long-payment-id-support if you really must use one, and warn the recipient they are using an obsolete feature that will disappear in the future."); \
return true; \
} \
} while(0)
@@ -149,7 +151,7 @@ namespace
const command_line::arg_descriptor<bool> arg_create_address_file = {"create-address-file", sw::tr("Create an address file for new wallets"), false};
const command_line::arg_descriptor<std::string> arg_subaddress_lookahead = {"subaddress-lookahead", tools::wallet2::tr("Set subaddress lookahead sizes to <major>:<minor>"), ""};
const command_line::arg_descriptor<bool> arg_use_english_language_names = {"use-english-language-names", sw::tr("Display English language names"), false};
- const command_line::arg_descriptor<bool> arg_long_payment_id_support = {"long-payment-id-support", sw::tr("Support obsolete long (unencrypted) payment ids"), false};
+ const command_line::arg_descriptor<bool> arg_long_payment_id_support = {"long-payment-id-support-bad-for-privacy", sw::tr("Support obsolete long (unencrypted) payment ids (using them harms your privacy)"), false};
const command_line::arg_descriptor< std::vector<std::string> > arg_command = {"command", ""};
@@ -5355,7 +5357,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
r = add_extra_nonce_to_tx_extra(extra, extra_nonce);
local_args.pop_back();
payment_id_seen = true;
- message_writer() << tr("Unencrypted payment IDs are bad for privacy: ask the recipient to use subaddresses instead");
+ message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead");
}
if(!r)
{
@@ -5465,7 +5467,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
{
LONG_PAYMENT_ID_SUPPORT_CHECK();
set_payment_id_to_tx_extra_nonce(extra_nonce, payment_id);
- message_writer() << tr("Unencrypted payment IDs are bad for privacy: ask the recipient to use subaddresses instead");
+ message_writer() << tr("Warning: Unencrypted payment IDs will harm your privacy: ask the recipient to use subaddresses instead");
}
else
{
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index b780c126e..4679a1697 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -512,7 +512,7 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, filename, std::string, String, true, std::string());
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, scan_from_height, uint64_t, Uint64, false, 0);
- const bool recover = field_scan_from_height_found;
+ const bool recover = true;
GET_FIELD_FROM_JSON_RETURN_ON_ERROR(json, password, std::string, String, false, std::string());
@@ -622,6 +622,8 @@ std::pair<std::unique_ptr<tools::wallet2>, tools::password_container> generate_f
wallet.reset(make_basic(vm, unattended, opts, password_prompter).release());
wallet->set_refresh_from_block_height(field_scan_from_height);
wallet->explicit_refresh_from_block_height(field_scan_from_height_found);
+ if (!old_language.empty())
+ wallet->set_seed_language(old_language);
try
{
@@ -2453,7 +2455,7 @@ void wallet2::process_parsed_blocks(uint64_t start_height, const std::vector<cry
" (height " + std::to_string(start_height) + "), local block id at this height: " +
string_tools::pod_to_hex(m_blockchain[current_index]));
- detach_blockchain(current_index);
+ detach_blockchain(current_index, output_tracker_cache);
process_new_blockchain_entry(bl, blocks[i], parsed_blocks[i], bl_id, current_index, tx_cache_data, tx_cache_data_offset, output_tracker_cache);
}
else
@@ -2897,7 +2899,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// MyMonero get_address_info needs to be called occasionally to trigger wallet sync.
// This call is not really needed for other purposes and can be removed if mymonero changes their backend.
- cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response res;
+ tools::COMMAND_RPC_GET_ADDRESS_INFO::response res;
// Get basic info
if(light_wallet_get_address_info(res)) {
@@ -3045,7 +3047,7 @@ void wallet2::refresh(bool trusted_daemon, uint64_t start_height, uint64_t & blo
// if we've got at least 10 blocks to refresh, assume we're starting
// a long refresh, and setup a tracking output cache if we need to
- if (m_track_uses && !output_tracker_cache && next_blocks.size() >= 10)
+ if (m_track_uses && (!output_tracker_cache || output_tracker_cache->empty()) && next_blocks.size() >= 10)
output_tracker_cache = create_output_tracker_cache();
// switch to the new blocks from the daemon
@@ -3186,7 +3188,7 @@ bool wallet2::get_rct_distribution(uint64_t &start_height, std::vector<uint64_t>
return true;
}
//----------------------------------------------------------------------------------------------------
-void wallet2::detach_blockchain(uint64_t height)
+void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache)
{
LOG_PRINT_L0("Detaching blockchain on height " << height);
@@ -3208,6 +3210,15 @@ void wallet2::detach_blockchain(uint64_t height)
}
}
+ for (transfer_details &td: m_transfers)
+ {
+ while (!td.m_uses.empty() && td.m_uses.back().first >= height)
+ td.m_uses.pop_back();
+ }
+
+ if (output_tracker_cache)
+ output_tracker_cache->clear();
+
auto it = std::find_if(m_transfers.begin(), m_transfers.end(), [&](const transfer_details& td){return td.m_block_height >= height;});
size_t i_start = it - m_transfers.begin();
@@ -3537,7 +3548,8 @@ void wallet2::change_password(const std::string &filename, const epee::wipeable_
decrypt_keys(original_password);
setup_keys(new_password);
rewrite(filename, new_password);
- store();
+ if (!filename.empty())
+ store();
}
//----------------------------------------------------------------------------------------------------
/*!
@@ -5226,7 +5238,8 @@ std::string wallet2::path() const
//----------------------------------------------------------------------------------------------------
void wallet2::store()
{
- store_to("", epee::wipeable_string());
+ if (!m_wallet_file.empty())
+ store_to("", epee::wipeable_string());
}
//----------------------------------------------------------------------------------------------------
void wallet2::store_to(const std::string &path, const epee::wipeable_string &password)
@@ -8408,8 +8421,8 @@ bool wallet2::light_wallet_login(bool &new_address)
{
MDEBUG("Light wallet login request");
m_light_wallet_connected = false;
- cryptonote::COMMAND_RPC_LOGIN::request request;
- cryptonote::COMMAND_RPC_LOGIN::response response;
+ tools::COMMAND_RPC_LOGIN::request request;
+ tools::COMMAND_RPC_LOGIN::response response;
request.address = get_account().get_public_address_str(m_nettype);
request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
// Always create account if it doesn't exist.
@@ -8433,10 +8446,10 @@ bool wallet2::light_wallet_login(bool &new_address)
return m_light_wallet_connected;
}
-bool wallet2::light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response)
+bool wallet2::light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response)
{
MDEBUG("Light wallet import wallet request");
- cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq;
+ tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::request oreq;
oreq.address = get_account().get_public_address_str(m_nettype);
oreq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
m_daemon_rpc_mutex.lock();
@@ -8452,8 +8465,8 @@ void wallet2::light_wallet_get_unspent_outs()
{
MDEBUG("Getting unspent outs");
- cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq;
- cryptonote::COMMAND_RPC_GET_UNSPENT_OUTS::response ores;
+ tools::COMMAND_RPC_GET_UNSPENT_OUTS::request oreq;
+ tools::COMMAND_RPC_GET_UNSPENT_OUTS::response ores;
oreq.amount = "0";
oreq.address = get_account().get_public_address_str(m_nettype);
@@ -8601,11 +8614,11 @@ void wallet2::light_wallet_get_unspent_outs()
}
}
-bool wallet2::light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response)
+bool wallet2::light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response)
{
MTRACE(__FUNCTION__);
- cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::request request;
+ tools::COMMAND_RPC_GET_ADDRESS_INFO::request request;
request.address = get_account().get_public_address_str(m_nettype);
request.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
@@ -8621,8 +8634,8 @@ void wallet2::light_wallet_get_address_txs()
{
MDEBUG("Refreshing light wallet");
- cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::request ireq;
- cryptonote::COMMAND_RPC_GET_ADDRESS_TXS::response ires;
+ tools::COMMAND_RPC_GET_ADDRESS_TXS::request ireq;
+ tools::COMMAND_RPC_GET_ADDRESS_TXS::response ires;
ireq.address = get_account().get_public_address_str(m_nettype);
ireq.view_key = string_tools::pod_to_hex(get_account().get_keys().m_view_secret_key);
@@ -9455,7 +9468,7 @@ bool wallet2::sanity_check(const std::vector<wallet2::pending_tx> &ptx_vector, s
THROW_WALLET_EXCEPTION_IF(ptx.change_dts.addr != ptx_vector[0].change_dts.addr, error::wallet_internal_error,
"Change goes to several different addresses");
const auto it = m_subaddresses.find(ptx_vector[0].change_dts.addr.m_spend_public_key);
- THROW_WALLET_EXCEPTION_IF(it == m_subaddresses.end(), error::wallet_internal_error, "Change address is not ours");
+ THROW_WALLET_EXCEPTION_IF(change > 0 && it == m_subaddresses.end(), error::wallet_internal_error, "Change address is not ours");
required[ptx_vector[0].change_dts.addr].first += change;
required[ptx_vector[0].change_dts.addr].second = ptx_vector[0].change_dts.is_subaddress;
@@ -9771,8 +9784,14 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_from(const crypton
}
uint64_t a = 0;
- for (size_t idx: unused_transfers_indices) a += m_transfers[idx].amount();
- for (size_t idx: unused_dust_indices) a += m_transfers[idx].amount();
+ for (const TX &tx: txes)
+ {
+ for (size_t idx: tx.selected_transfers)
+ {
+ a += m_transfers[idx].amount();
+ }
+ a -= tx.ptx.fee;
+ }
std::vector<cryptonote::tx_destination_entry> synthetic_dsts(1, cryptonote::tx_destination_entry("", a, address, is_subaddress));
THROW_WALLET_EXCEPTION_IF(!sanity_check(ptx_vector, synthetic_dsts), error::wallet_internal_error, "Created transaction(s) failed sanity check");
@@ -9856,7 +9875,7 @@ bool wallet2::use_fork_rules(uint8_t version, int64_t early_blocks) const
result = m_node_rpc_proxy.get_earliest_height(version, earliest_height);
throw_on_rpc_response_error(result, "get_hard_fork_info");
- bool close_enough = height >= earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand
+ bool close_enough = (int64_t)height >= (int64_t)earliest_height - early_blocks && earliest_height != std::numeric_limits<uint64_t>::max(); // start using the rules that many blocks beforehand
if (close_enough)
LOG_PRINT_L2("Using v" << (unsigned)version << " rules");
else
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 0d13235bd..28ebd6704 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -62,6 +62,7 @@
#include "common/password.h"
#include "node_rpc_proxy.h"
#include "message_store.h"
+#include "wallet_light_rpc.h"
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.wallet2"
@@ -1178,11 +1179,11 @@ namespace tools
// fetch txs and store in m_payments
void light_wallet_get_address_txs();
// get_address_info
- bool light_wallet_get_address_info(cryptonote::COMMAND_RPC_GET_ADDRESS_INFO::response &response);
+ bool light_wallet_get_address_info(tools::COMMAND_RPC_GET_ADDRESS_INFO::response &response);
// Login. new_address is true if address hasn't been used on lw node before.
bool light_wallet_login(bool &new_address);
// Send an import request to lw node. returns info about import fee, address and payment_id
- bool light_wallet_import_wallet_request(cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response);
+ bool light_wallet_import_wallet_request(tools::COMMAND_RPC_IMPORT_WALLET_REQUEST::response &response);
// get random outputs from light wallet server
void light_wallet_get_outs(std::vector<std::vector<get_outs_entry>> &outs, const std::vector<size_t> &selected_transfers, size_t fake_outputs_count);
// Parse rct string
@@ -1280,7 +1281,7 @@ namespace tools
bool load_keys(const std::string& keys_file_name, const epee::wipeable_string& password);
void process_new_transaction(const crypto::hash &txid, const cryptonote::transaction& tx, const std::vector<uint64_t> &o_indices, uint64_t height, uint64_t ts, bool miner_tx, bool pool, bool double_spend_seen, const tx_cache_data &tx_cache_data, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void process_new_blockchain_entry(const cryptonote::block& b, const cryptonote::block_complete_entry& bche, const parsed_block &parsed_block, const crypto::hash& bl_id, uint64_t height, const std::vector<tx_cache_data> &tx_cache_data, size_t tx_cache_data_offset, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
- void detach_blockchain(uint64_t height);
+ void detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, uint64_t>, size_t> *output_tracker_cache = NULL);
void get_short_chain_history(std::list<crypto::hash>& ids, uint64_t granularity = 1) const;
bool clear();
void clear_soft(bool keep_key_images=false);
diff --git a/src/wallet/wallet_light_rpc.h b/src/wallet/wallet_light_rpc.h
new file mode 100644
index 000000000..1d35cec33
--- /dev/null
+++ b/src/wallet/wallet_light_rpc.h
@@ -0,0 +1,320 @@
+// Copyright (c) 2014-2018, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+//
+// Parts of this file are originally copyright (c) 2012-2013 The Cryptonote developers
+
+#pragma once
+#include "cryptonote_basic/cryptonote_basic.h"
+#include "crypto/hash.h"
+
+namespace tools
+{
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_ADDRESS_TXS
+ {
+ struct request_t
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct spent_output {
+ uint64_t amount;
+ std::string key_image;
+ std::string tx_pub_key;
+ uint64_t out_index;
+ uint32_t mixin;
+
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(key_image)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(out_index)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct transaction
+ {
+ uint64_t id;
+ std::string hash;
+ uint64_t timestamp;
+ uint64_t total_received;
+ uint64_t total_sent;
+ uint64_t unlock_time;
+ uint64_t height;
+ std::list<spent_output> spent_outputs;
+ std::string payment_id;
+ bool coinbase;
+ bool mempool;
+ uint32_t mixin;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(id)
+ KV_SERIALIZE(hash)
+ KV_SERIALIZE(timestamp)
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_sent)
+ KV_SERIALIZE(unlock_time)
+ KV_SERIALIZE(height)
+ KV_SERIALIZE(spent_outputs)
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(coinbase)
+ KV_SERIALIZE(mempool)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+
+ struct response_t
+ {
+ //std::list<std::string> txs_as_json;
+ uint64_t total_received;
+ uint64_t total_received_unlocked = 0; // OpenMonero only
+ uint64_t scanned_height;
+ std::vector<transaction> transactions;
+ uint64_t blockchain_height;
+ uint64_t scanned_block_height;
+ std::string status;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_received_unlocked)
+ KV_SERIALIZE(scanned_height)
+ KV_SERIALIZE(transactions)
+ KV_SERIALIZE(blockchain_height)
+ KV_SERIALIZE(scanned_block_height)
+ KV_SERIALIZE(status)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_ADDRESS_INFO
+ {
+ struct request_t
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct spent_output
+ {
+ uint64_t amount;
+ std::string key_image;
+ std::string tx_pub_key;
+ uint64_t out_index;
+ uint32_t mixin;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(key_image)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(out_index)
+ KV_SERIALIZE(mixin)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response_t
+ {
+ uint64_t locked_funds;
+ uint64_t total_received;
+ uint64_t total_sent;
+ uint64_t scanned_height;
+ uint64_t scanned_block_height;
+ uint64_t start_height;
+ uint64_t transaction_height;
+ uint64_t blockchain_height;
+ std::list<spent_output> spent_outputs;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(locked_funds)
+ KV_SERIALIZE(total_received)
+ KV_SERIALIZE(total_sent)
+ KV_SERIALIZE(scanned_height)
+ KV_SERIALIZE(scanned_block_height)
+ KV_SERIALIZE(start_height)
+ KV_SERIALIZE(transaction_height)
+ KV_SERIALIZE(blockchain_height)
+ KV_SERIALIZE(spent_outputs)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
+ //-----------------------------------------------
+ struct COMMAND_RPC_GET_UNSPENT_OUTS
+ {
+ struct request_t
+ {
+ std::string amount;
+ std::string address;
+ std::string view_key;
+ // OpenMonero specific
+ uint64_t mixin;
+ bool use_dust;
+ std::string dust_threshold;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ KV_SERIALIZE(mixin)
+ KV_SERIALIZE(use_dust)
+ KV_SERIALIZE(dust_threshold)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+
+ struct output {
+ uint64_t amount;
+ std::string public_key;
+ uint64_t index;
+ uint64_t global_index;
+ std::string rct;
+ std::string tx_hash;
+ std::string tx_pub_key;
+ std::string tx_prefix_hash;
+ std::vector<std::string> spend_key_images;
+ uint64_t timestamp;
+ uint64_t height;
+
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(public_key)
+ KV_SERIALIZE(index)
+ KV_SERIALIZE(global_index)
+ KV_SERIALIZE(rct)
+ KV_SERIALIZE(tx_hash)
+ KV_SERIALIZE(tx_pub_key)
+ KV_SERIALIZE(tx_prefix_hash)
+ KV_SERIALIZE(spend_key_images)
+ KV_SERIALIZE(timestamp)
+ KV_SERIALIZE(height)
+ END_KV_SERIALIZE_MAP()
+ };
+
+ struct response_t
+ {
+ uint64_t amount;
+ std::list<output> outputs;
+ uint64_t per_kb_fee;
+ std::string status;
+ std::string reason;
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(amount)
+ KV_SERIALIZE(outputs)
+ KV_SERIALIZE(per_kb_fee)
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(reason)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+ //-----------------------------------------------
+ struct COMMAND_RPC_LOGIN
+ {
+ struct request_t
+ {
+ std::string address;
+ std::string view_key;
+ bool create_account;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ KV_SERIALIZE(create_account)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ std::string status;
+ std::string reason;
+ bool new_address;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(status)
+ KV_SERIALIZE(reason)
+ KV_SERIALIZE(new_address)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+ //-----------------------------------------------
+ struct COMMAND_RPC_IMPORT_WALLET_REQUEST
+ {
+ struct request_t
+ {
+ std::string address;
+ std::string view_key;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(address)
+ KV_SERIALIZE(view_key)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ std::string payment_id;
+ uint64_t import_fee;
+ bool new_request;
+ bool request_fulfilled;
+ std::string payment_address;
+ std::string status;
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE(payment_id)
+ KV_SERIALIZE(import_fee)
+ KV_SERIALIZE(new_request)
+ KV_SERIALIZE(request_fulfilled)
+ KV_SERIALIZE(payment_address)
+ KV_SERIALIZE(status)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+ //-----------------------------------------------
+}
diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp
index a1f60ea01..f80263007 100644
--- a/src/wallet/wallet_rpc_server.cpp
+++ b/src/wallet/wallet_rpc_server.cpp
@@ -56,6 +56,8 @@ using namespace epee;
#undef MONERO_DEFAULT_LOG_CATEGORY
#define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc"
+#define DEFAULT_AUTO_REFRESH_PERIOD 20 // seconds
+
namespace
{
const command_line::arg_descriptor<std::string, true> arg_rpc_bind_port = {"rpc-bind-port", "Sets bind port for server"};
@@ -124,13 +126,18 @@ namespace tools
{
m_stop = false;
m_net_server.add_idle_handler([this](){
+ if (m_auto_refresh_period == 0) // disabled
+ return true;
+ if (boost::posix_time::microsec_clock::universal_time() < m_last_auto_refresh_time + boost::posix_time::seconds(m_auto_refresh_period))
+ return true;
try {
if (m_wallet) m_wallet->refresh(m_wallet->is_trusted_daemon());
} catch (const std::exception& ex) {
LOG_ERROR("Exception at while refreshing, what=" << ex.what());
}
+ m_last_auto_refresh_time = boost::posix_time::microsec_clock::universal_time();
return true;
- }, 20000);
+ }, 1000);
m_net_server.add_idle_handler([this](){
if (m_stop.load(std::memory_order_relaxed))
{
@@ -263,6 +270,9 @@ namespace tools
std::vector<std::vector<uint8_t>> allowed_fingerprints{ rpc_ssl_allowed_fingerprints.size() };
std::transform(rpc_ssl_allowed_fingerprints.begin(), rpc_ssl_allowed_fingerprints.end(), allowed_fingerprints.begin(), epee::from_hex::vector);
+ m_auto_refresh_period = DEFAULT_AUTO_REFRESH_PERIOD;
+ m_last_auto_refresh_time = boost::posix_time::min_date_time;
+
m_net_server.set_threads_prefix("RPC");
auto rng = [](size_t len, uint8_t *ptr) { return crypto::rand(len, ptr); };
return epee::http_server_impl_base<wallet_rpc_server, connection_context>::init(
@@ -891,15 +901,7 @@ namespace tools
try
{
- uint64_t mixin;
- if(req.ring_size != 0)
- {
- mixin = m_wallet->adjust_mixin(req.ring_size - 1);
- }
- else
- {
- mixin = m_wallet->adjust_mixin(req.mixin);
- }
+ uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices);
@@ -951,15 +953,7 @@ namespace tools
try
{
- uint64_t mixin;
- if(req.ring_size != 0)
- {
- mixin = m_wallet->adjust_mixin(req.ring_size - 1);
- }
- else
- {
- mixin = m_wallet->adjust_mixin(req.mixin);
- }
+ uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
LOG_PRINT_L2("on_transfer_split calling create_transactions_2");
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_2(dsts, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices);
@@ -1369,15 +1363,7 @@ namespace tools
try
{
- uint64_t mixin;
- if(req.ring_size != 0)
- {
- mixin = m_wallet->adjust_mixin(req.ring_size - 1);
- }
- else
- {
- mixin = m_wallet->adjust_mixin(req.mixin);
- }
+ uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_all(req.below_amount, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra, req.account_index, req.subaddr_indices);
@@ -1432,15 +1418,7 @@ namespace tools
try
{
- uint64_t mixin;
- if(req.ring_size != 0)
- {
- mixin = m_wallet->adjust_mixin(req.ring_size - 1);
- }
- else
- {
- mixin = m_wallet->adjust_mixin(req.mixin);
- }
+ uint64_t mixin = m_wallet->adjust_mixin(req.ring_size ? req.ring_size - 1 : 0);
uint32_t priority = m_wallet->adjust_priority(req.priority);
std::vector<wallet2::pending_tx> ptx_vector = m_wallet->create_transactions_single(ki, dsts[0].addr, dsts[0].is_subaddress, req.outputs, mixin, req.unlock_time, priority, extra);
@@ -2834,6 +2812,28 @@ namespace tools
return true;
}
//------------------------------------------------------------------------------------------------------------------------------
+ bool wallet_rpc_server::on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx)
+ {
+ if (m_restricted)
+ {
+ er.code = WALLET_RPC_ERROR_CODE_DENIED;
+ er.message = "Command unavailable in restricted mode.";
+ return false;
+ }
+ try
+ {
+ m_auto_refresh_period = req.enable ? req.period ? req.period : DEFAULT_AUTO_REFRESH_PERIOD : 0;
+ MINFO("Auto refresh now " << (m_auto_refresh_period ? std::to_string(m_auto_refresh_period) + " seconds" : std::string("disabled")));
+ return true;
+ }
+ catch (const std::exception& e)
+ {
+ handle_rpc_exception(std::current_exception(), er, WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR);
+ return false;
+ }
+ return true;
+ }
+ //------------------------------------------------------------------------------------------------------------------------------
bool wallet_rpc_server::on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx)
{
if (!m_wallet) return not_open(er);
@@ -2936,7 +2936,7 @@ namespace tools
er.message = "Invalid filename";
return false;
}
- std::string wallet_file = m_wallet_dir + "/" + req.filename;
+ std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename);
{
std::vector<std::string> languages;
crypto::ElectrumWords::get_language_list(languages);
@@ -3221,12 +3221,6 @@ namespace tools
}
// early check for mandatory fields
- if (req.filename.empty())
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to.";
- return false;
- }
if (req.viewkey.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@@ -3255,7 +3249,7 @@ namespace tools
er.message = "Invalid filename";
return false;
}
- std::string wallet_file = m_wallet_dir + "/" + req.filename;
+ std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename);
// check if wallet file already exists
if (!wallet_file.empty())
{
@@ -3365,7 +3359,8 @@ namespace tools
{
try
{
- m_wallet->store();
+ if (!wallet_file.empty())
+ m_wallet->store();
}
catch (const std::exception &e)
{
@@ -3389,12 +3384,6 @@ namespace tools
}
// early check for mandatory fields
- if (req.filename.empty())
- {
- er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
- er.message = "field 'filename' is mandatory. Please provide a filename to save the restored wallet to.";
- return false;
- }
if (req.seed.empty())
{
er.code = WALLET_RPC_ERROR_CODE_UNKNOWN_ERROR;
@@ -3417,7 +3406,7 @@ namespace tools
er.message = "Invalid filename";
return false;
}
- std::string wallet_file = m_wallet_dir + "/" + req.filename;
+ std::string wallet_file = req.filename.empty() ? "" : (m_wallet_dir + "/" + req.filename);
// check if wallet file already exists
if (!wallet_file.empty())
{
@@ -3533,7 +3522,7 @@ namespace tools
er.message = "Failed to encode seed";
return false;
}
- res.seed = electrum_words.data();
+ res.seed = std::string(electrum_words.data(), electrum_words.size());
if (!wal)
{
diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h
index affaf10f7..2b52275b8 100644
--- a/src/wallet/wallet_rpc_server.h
+++ b/src/wallet/wallet_rpc_server.h
@@ -129,6 +129,7 @@ namespace tools
MAP_JON_RPC_WE("add_address_book", on_add_address_book, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("delete_address_book",on_delete_address_book,wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY)
MAP_JON_RPC_WE("refresh", on_refresh, wallet_rpc::COMMAND_RPC_REFRESH)
+ MAP_JON_RPC_WE("auto_refresh", on_auto_refresh, wallet_rpc::COMMAND_RPC_AUTO_REFRESH)
MAP_JON_RPC_WE("rescan_spent", on_rescan_spent, wallet_rpc::COMMAND_RPC_RESCAN_SPENT)
MAP_JON_RPC_WE("start_mining", on_start_mining, wallet_rpc::COMMAND_RPC_START_MINING)
MAP_JON_RPC_WE("stop_mining", on_stop_mining, wallet_rpc::COMMAND_RPC_STOP_MINING)
@@ -210,6 +211,7 @@ namespace tools
bool on_add_address_book(const wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_ADD_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_delete_address_book(const wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::request& req, wallet_rpc::COMMAND_RPC_DELETE_ADDRESS_BOOK_ENTRY::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_refresh(const wallet_rpc::COMMAND_RPC_REFRESH::request& req, wallet_rpc::COMMAND_RPC_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
+ bool on_auto_refresh(const wallet_rpc::COMMAND_RPC_AUTO_REFRESH::request& req, wallet_rpc::COMMAND_RPC_AUTO_REFRESH::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_rescan_spent(const wallet_rpc::COMMAND_RPC_RESCAN_SPENT::request& req, wallet_rpc::COMMAND_RPC_RESCAN_SPENT::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_start_mining(const wallet_rpc::COMMAND_RPC_START_MINING::request& req, wallet_rpc::COMMAND_RPC_START_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
bool on_stop_mining(const wallet_rpc::COMMAND_RPC_STOP_MINING::request& req, wallet_rpc::COMMAND_RPC_STOP_MINING::response& res, epee::json_rpc::error& er, const connection_context *ctx = NULL);
@@ -256,5 +258,7 @@ namespace tools
std::atomic<bool> m_stop;
bool m_restricted;
const boost::program_options::variables_map *m_vm;
+ uint32_t m_auto_refresh_period;
+ boost::posix_time::ptime m_last_auto_refresh_time;
};
}
diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h
index 36775fa1e..de3067a52 100644
--- a/src/wallet/wallet_rpc_server_commands_defs.h
+++ b/src/wallet/wallet_rpc_server_commands_defs.h
@@ -446,7 +446,6 @@ namespace wallet_rpc
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
uint32_t priority;
- uint64_t mixin;
uint64_t ring_size;
uint64_t unlock_time;
std::string payment_id;
@@ -460,7 +459,6 @@ namespace wallet_rpc
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
- KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
@@ -505,7 +503,6 @@ namespace wallet_rpc
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
uint32_t priority;
- uint64_t mixin;
uint64_t ring_size;
uint64_t unlock_time;
std::string payment_id;
@@ -519,7 +516,6 @@ namespace wallet_rpc
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
- KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
KV_SERIALIZE(unlock_time)
KV_SERIALIZE(payment_id)
@@ -746,7 +742,6 @@ namespace wallet_rpc
uint32_t account_index;
std::set<uint32_t> subaddr_indices;
uint32_t priority;
- uint64_t mixin;
uint64_t ring_size;
uint64_t outputs;
uint64_t unlock_time;
@@ -762,7 +757,6 @@ namespace wallet_rpc
KV_SERIALIZE(account_index)
KV_SERIALIZE(subaddr_indices)
KV_SERIALIZE(priority)
- KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
KV_SERIALIZE_OPT(outputs, (uint64_t)1)
KV_SERIALIZE(unlock_time)
@@ -816,7 +810,6 @@ namespace wallet_rpc
{
std::string address;
uint32_t priority;
- uint64_t mixin;
uint64_t ring_size;
uint64_t outputs;
uint64_t unlock_time;
@@ -830,7 +823,6 @@ namespace wallet_rpc
BEGIN_KV_SERIALIZE_MAP()
KV_SERIALIZE(address)
KV_SERIALIZE(priority)
- KV_SERIALIZE_OPT(mixin, (uint64_t)0)
KV_SERIALIZE_OPT(ring_size, (uint64_t)0)
KV_SERIALIZE_OPT(outputs, (uint64_t)1)
KV_SERIALIZE(unlock_time)
@@ -1932,6 +1924,28 @@ namespace wallet_rpc
typedef epee::misc_utils::struct_init<response_t> response;
};
+ struct COMMAND_RPC_AUTO_REFRESH
+ {
+ struct request_t
+ {
+ bool enable;
+ uint32_t period; // seconds
+
+ BEGIN_KV_SERIALIZE_MAP()
+ KV_SERIALIZE_OPT(enable, true)
+ KV_SERIALIZE_OPT(period, (uint32_t)0)
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<request_t> request;
+
+ struct response_t
+ {
+ BEGIN_KV_SERIALIZE_MAP()
+ END_KV_SERIALIZE_MAP()
+ };
+ typedef epee::misc_utils::struct_init<response_t> response;
+ };
+
struct COMMAND_RPC_START_MINING
{
struct request_t
diff --git a/tests/README.md b/tests/README.md
index 001ab6154..053dd2244 100644
--- a/tests/README.md
+++ b/tests/README.md
@@ -18,7 +18,7 @@ Tests are located in `tests/core_tests/`, and follow a straightforward naming co
To run only Monero's core tests (after building):
```
-cd build/debug/tests/core
+cd build/debug/tests/core_tests
ctest
```
@@ -103,6 +103,8 @@ cd build/debug/tests/performance_tests
./performance_tests
```
+The path may be build/Linux/master/debug (adapt as necessary for your platform).
+
If the `performance_tests` binary does not exist, try running `make` in the `build/debug/tests/performance_tests` directory.
To run the same tests on a release build, replace `debug` with `release`.
diff --git a/tests/block_weight/block_weight.cpp b/tests/block_weight/block_weight.cpp
index 1e8974b78..7b3fdfe57 100644
--- a/tests/block_weight/block_weight.cpp
+++ b/tests/block_weight/block_weight.cpp
@@ -84,6 +84,11 @@ public:
while (count-- && start_height < blocks.size()) ret.push_back(blocks[start_height++].long_term_weight);
return ret;
}
+ virtual crypto::hash get_block_hash_from_height(const uint64_t &height) const override {
+ crypto::hash hash = crypto::null_hash;
+ *(uint64_t*)&hash = height;
+ return hash;
+ }
virtual crypto::hash top_block_hash(uint64_t *block_height = NULL) const override {
uint64_t h = height();
crypto::hash top = crypto::null_hash;
diff --git a/tests/difficulty/CMakeLists.txt b/tests/difficulty/CMakeLists.txt
index 2ed495806..fb0dd6b9e 100644
--- a/tests/difficulty/CMakeLists.txt
+++ b/tests/difficulty/CMakeLists.txt
@@ -45,3 +45,6 @@ set_property(TARGET difficulty-tests
add_test(
NAME difficulty
COMMAND difficulty-tests "${CMAKE_CURRENT_SOURCE_DIR}/data.txt")
+add_test(
+ NAME wide_difficulty
+ COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/wide_difficulty.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}/gen_wide_data.py" "${CMAKE_CURRENT_BINARY_DIR}/difficulty-tests" "${CMAKE_CURRENT_BINARY_DIR}/wide_data.txt")
diff --git a/tests/difficulty/difficulty.cpp b/tests/difficulty/difficulty.cpp
index ee20e27e4..9985b8710 100644
--- a/tests/difficulty/difficulty.cpp
+++ b/tests/difficulty/difficulty.cpp
@@ -43,16 +43,62 @@ using namespace std;
#define DEFAULT_TEST_DIFFICULTY_TARGET 120
+static int test_wide_difficulty(const char *filename)
+{
+ std::vector<uint64_t> timestamps;
+ std::vector<cryptonote::difficulty_type> cumulative_difficulties;
+ fstream data(filename, fstream::in);
+ data.exceptions(fstream::badbit);
+ data.clear(data.rdstate());
+ uint64_t timestamp;
+ cryptonote::difficulty_type difficulty, cumulative_difficulty = 0;
+ size_t n = 0;
+ while (data >> timestamp >> difficulty) {
+ size_t begin, end;
+ if (n < DIFFICULTY_WINDOW + DIFFICULTY_LAG) {
+ begin = 0;
+ end = min(n, (size_t) DIFFICULTY_WINDOW);
+ } else {
+ end = n - DIFFICULTY_LAG;
+ begin = end - DIFFICULTY_WINDOW;
+ }
+ cryptonote::difficulty_type res = cryptonote::next_difficulty(
+ std::vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end),
+ std::vector<cryptonote::difficulty_type>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET);
+ if (res != difficulty) {
+ cerr << "Wrong wide difficulty for block " << n << endl
+ << "Expected: " << difficulty << endl
+ << "Found: " << res << endl;
+ return 1;
+ }
+ timestamps.push_back(timestamp);
+ cumulative_difficulties.push_back(cumulative_difficulty += difficulty);
+ ++n;
+ }
+ if (!data.eof()) {
+ data.clear(fstream::badbit);
+ }
+ return 0;
+}
+
int main(int argc, char *argv[]) {
- if (argc != 2) {
+ if (argc < 2) {
cerr << "Wrong arguments" << endl;
return 1;
}
+ if (argc == 3 && strcmp(argv[1], "--wide") == 0)
+ {
+ return test_wide_difficulty(argv[2]);
+ }
+
vector<uint64_t> timestamps, cumulative_difficulties;
+ std::vector<cryptonote::difficulty_type> wide_cumulative_difficulties;
fstream data(argv[1], fstream::in);
data.exceptions(fstream::badbit);
data.clear(data.rdstate());
- uint64_t timestamp, difficulty, cumulative_difficulty = 0;
+ uint64_t timestamp;
+ uint64_t difficulty, cumulative_difficulty = 0;
+ cryptonote::difficulty_type wide_cumulative_difficulty = 0;
size_t n = 0;
while (data >> timestamp >> difficulty) {
size_t begin, end;
@@ -63,17 +109,27 @@ int main(int argc, char *argv[]) {
end = n - DIFFICULTY_LAG;
begin = end - DIFFICULTY_WINDOW;
}
- uint64_t res = cryptonote::next_difficulty(
+ uint64_t res = cryptonote::next_difficulty_64(
vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end),
- vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET);
+ std::vector<uint64_t>(cumulative_difficulties.begin() + begin, cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET);
if (res != difficulty) {
cerr << "Wrong difficulty for block " << n << endl
<< "Expected: " << difficulty << endl
<< "Found: " << res << endl;
return 1;
}
+ cryptonote::difficulty_type wide_res = cryptonote::next_difficulty(
+ std::vector<uint64_t>(timestamps.begin() + begin, timestamps.begin() + end),
+ std::vector<cryptonote::difficulty_type>(wide_cumulative_difficulties.begin() + begin, wide_cumulative_difficulties.begin() + end), DEFAULT_TEST_DIFFICULTY_TARGET);
+ if (wide_res.convert_to<uint64_t>() != res) {
+ cerr << "Wrong wide difficulty for block " << n << endl
+ << "Expected: " << res << endl
+ << "Found: " << wide_res << endl;
+ return 1;
+ }
timestamps.push_back(timestamp);
cumulative_difficulties.push_back(cumulative_difficulty += difficulty);
+ wide_cumulative_difficulties.push_back(wide_cumulative_difficulty += difficulty);
++n;
}
if (!data.eof()) {
diff --git a/tests/difficulty/gen_wide_data.py b/tests/difficulty/gen_wide_data.py
new file mode 100644
index 000000000..64af4e208
--- /dev/null
+++ b/tests/difficulty/gen_wide_data.py
@@ -0,0 +1,47 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import random
+
+DIFFICULTY_TARGET = 120
+DIFFICULTY_WINDOW = 720
+DIFFICULTY_LAG = 15
+DIFFICULTY_CUT = 60
+
+def difficulty():
+ times = []
+ diffs = []
+ while True:
+ if len(times) <= 1:
+ diff = 1
+ else:
+ begin = max(len(times) - DIFFICULTY_WINDOW - DIFFICULTY_LAG, 0)
+ end = min(begin + DIFFICULTY_WINDOW, len(times))
+ length = end - begin
+ assert length >= 2
+ if length <= DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT:
+ cut_begin = 0
+ cut_end = length
+ else:
+ excess = length - (DIFFICULTY_WINDOW - 2 * DIFFICULTY_CUT)
+ cut_begin = (excess + 1) // 2
+ cut_end = length - excess // 2
+ assert cut_begin + 2 <= cut_end
+ wnd = times[begin:end]
+ wnd.sort()
+ dtime = wnd[cut_end - 1] - wnd[cut_begin]
+ dtime = max(dtime, 1)
+ ddiff = sum(diffs[begin + cut_begin + 1:begin + cut_end])
+ diff = (ddiff * DIFFICULTY_TARGET + dtime - 1) // dtime
+ times.append((yield diff))
+ diffs.append(diff)
+
+random.seed(1)
+time = 1000
+gen = difficulty()
+diff = next(gen)
+for i in range(100000):
+ power = 100 if i < 10000 else 100000000 if i < 500 else 1000000000000 if i < 1000 else 1000000000000000 if i < 2000 else 10000000000000000000 if i < 4000 else 1000000000000000000000000
+ time += random.randint(-diff // power - 10, 3 * diff // power + 10)
+ print(time, diff)
+ diff = gen.send(time)
diff --git a/tests/difficulty/wide_difficulty.py b/tests/difficulty/wide_difficulty.py
new file mode 100755
index 000000000..41a2a632d
--- /dev/null
+++ b/tests/difficulty/wide_difficulty.py
@@ -0,0 +1,22 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import sys
+import subprocess
+
+python = sys.argv[1]
+py = sys.argv[2]
+c = sys.argv[3]
+data = sys.argv[4]
+
+first = python + " " + py + " > " + data
+second = [c, '--wide', data]
+
+try:
+ print('running: ', first)
+ subprocess.check_call(first, shell=True)
+ print('running: ', second)
+ subprocess.check_call(second)
+except:
+ sys.exit(1)
+
diff --git a/tests/functional_tests/CMakeLists.txt b/tests/functional_tests/CMakeLists.txt
index 2e3519994..60060f56f 100644
--- a/tests/functional_tests/CMakeLists.txt
+++ b/tests/functional_tests/CMakeLists.txt
@@ -49,6 +49,7 @@ target_link_libraries(functional_tests
${Boost_PROGRAM_OPTIONS_LIBRARY}
${CMAKE_THREAD_LIBS_INIT}
${EXTRA_LIBRARIES})
-set_property(TARGET functional_tests
- PROPERTY
- FOLDER "tests")
+
+add_test(
+ NAME functional_tests_rpc
+ COMMAND ${PYTHON_EXECUTABLE} "${CMAKE_CURRENT_SOURCE_DIR}/functional_tests_rpc.py" "${PYTHON_EXECUTABLE}" "${CMAKE_CURRENT_SOURCE_DIR}" "${CMAKE_BINARY_DIR}" all)
diff --git a/tests/functional_tests/blockchain.py b/tests/functional_tests/blockchain.py
index 983658a7c..d805fccda 100755
--- a/tests/functional_tests/blockchain.py
+++ b/tests/functional_tests/blockchain.py
@@ -28,75 +28,129 @@
# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-"""Test blockchain RPC calls
+import time
+
+"""Test daemon blockchain RPC calls
Test the following RPCs:
- get_info
- generateblocks
+ - misc block retrieval
+ - pop_blocks
- [TODO: many tests still need to be written]
"""
-from test_framework.daemon import Daemon
-from test_framework.wallet import Wallet
+from framework.daemon import Daemon
class BlockchainTest():
def run_test(self):
- self._test_get_info()
- self._test_hardfork_info()
self._test_generateblocks(5)
- def _test_get_info(self):
- print('Test get_info')
-
- daemon = Daemon()
- res = daemon.get_info()
-
- # difficulty should be set to 1 for this test
- assert 'difficulty' in res.keys()
- assert res['difficulty'] == 1;
-
- # nettype should not be TESTNET
- assert 'testnet' in res.keys()
- assert res['testnet'] == False;
-
- # nettype should not be STAGENET
- assert 'stagenet' in res.keys()
- assert res['stagenet'] == False;
-
- # nettype should be FAKECHAIN
- assert 'nettype' in res.keys()
- assert res['nettype'] == "fakechain";
-
- # free_space should be > 0
- assert 'free_space' in res.keys()
- assert res['free_space'] > 0
-
- # height should be greater or equal to 1
- assert 'height' in res.keys()
- assert res['height'] >= 1
-
-
- def _test_hardfork_info(self):
- print('Test hard_fork_info')
-
- daemon = Daemon()
- res = daemon.hard_fork_info()
-
- # hard_fork version should be set at height 1
- assert 'earliest_height' in res.keys()
- assert res['earliest_height'] == 1;
-
-
def _test_generateblocks(self, blocks):
- print("Test generating", blocks, 'blocks')
+ assert blocks >= 2
+
+ print "Test generating", blocks, 'blocks'
daemon = Daemon()
- res = daemon.get_info()
- height = res['height']
- res = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
- assert res['height'] == height + blocks - 1
+ # check info/height before generating blocks
+ res_info = daemon.get_info()
+ height = res_info.height
+ prev_block = res_info.top_block_hash
+ res_height = daemon.get_height()
+ assert res_height.height == height
+ assert int(res_info.wide_cumulative_difficulty) == (res_info.cumulative_difficulty_top64 << 64) + res_info.cumulative_difficulty
+ cumulative_difficulty = int(res_info.wide_cumulative_difficulty)
+
+ # we should not see a block at height
+ ok = False
+ try: daemon.getblock(height)
+ except: ok = True
+ assert ok
+
+ # generate blocks
+ res_generateblocks = daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', blocks)
+
+ # check info/height after generateblocks blocks
+ assert res_generateblocks.height == height + blocks - 1
+ res_info = daemon.get_info()
+ assert res_info.height == height + blocks
+ assert res_info.top_block_hash != prev_block
+ res_height = daemon.get_height()
+ assert res_height.height == height + blocks
+
+ # get the blocks, check they have the right height
+ res_getblock = []
+ for n in range(blocks):
+ res_getblock.append(daemon.getblock(height + n))
+ block_header = res_getblock[n].block_header
+ assert abs(block_header.timestamp - time.time()) < 10 # within 10 seconds
+ assert block_header.height == height + n
+ assert block_header.orphan_status == False
+ assert block_header.depth == blocks - n - 1
+ assert block_header.prev_hash == prev_block, prev_block
+ assert int(block_header.wide_difficulty) == (block_header.difficulty_top64 << 64) + block_header.difficulty
+ assert int(block_header.wide_cumulative_difficulty) == (block_header.cumulative_difficulty_top64 << 64) + block_header.cumulative_difficulty
+ assert block_header.reward >= 600000000000 # tail emission
+ cumulative_difficulty += int(block_header.wide_difficulty)
+ assert cumulative_difficulty == int(block_header.wide_cumulative_difficulty)
+ assert block_header.block_size > 0
+ assert block_header.block_weight >= block_header.block_size
+ assert block_header.long_term_weight > 0
+ prev_block = block_header.hash
+
+ # we should not see a block after that
+ ok = False
+ try: daemon.getblock(height + blocks)
+ except: ok = True
+ assert ok
+
+ # getlastblockheader and by height/hash should return the same block
+ res_getlastblockheader = daemon.getlastblockheader()
+ assert res_getlastblockheader.block_header == block_header
+ res_getblockheaderbyhash = daemon.getblockheaderbyhash(prev_block)
+ assert res_getblockheaderbyhash.block_header == block_header
+ res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 1)
+ assert res_getblockheaderbyheight.block_header == block_header
+
+ # getting a block template after that should have the right height, etc
+ res_getblocktemplate = daemon.getblocktemplate('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
+ assert res_getblocktemplate.height == height + blocks
+ assert res_getblocktemplate.reserved_offset > 0
+ assert res_getblocktemplate.prev_hash == res_info.top_block_hash
+ assert res_getblocktemplate.expected_reward >= 600000000000
+ assert len(res_getblocktemplate.blocktemplate_blob) > 0
+ assert len(res_getblocktemplate.blockhashing_blob) > 0
+ assert int(res_getblocktemplate.wide_difficulty) == (res_getblocktemplate.difficulty_top64 << 64) + res_getblocktemplate.difficulty
+
+ # diff etc should be the same
+ assert res_getblocktemplate.prev_hash == res_info.top_block_hash
+
+ res_getlastblockheader = daemon.getlastblockheader()
+
+ # pop a block
+ res_popblocks = daemon.pop_blocks(1)
+ assert res_popblocks.height == height + blocks - 1
+
+ res_info = daemon.get_info()
+ assert res_info.height == height + blocks - 1
+
+ # getlastblockheader and by height/hash should return the previous block
+ block_header = res_getblock[blocks - 2].block_header
+ block_header.depth = 0 # this will be different, ignore it
+ res_getlastblockheader = daemon.getlastblockheader()
+ assert res_getlastblockheader.block_header == block_header
+ res_getblockheaderbyhash = daemon.getblockheaderbyhash(block_header.hash)
+ assert res_getblockheaderbyhash.block_header == block_header
+ res_getblockheaderbyheight = daemon.getblockheaderbyheight(height + blocks - 2)
+ assert res_getblockheaderbyheight.block_header == block_header
+
+ # we should not see the popped block anymore
+ ok = False
+ try: daemon.getblock(height + blocks - 1)
+ except: ok = True
+ assert ok
if __name__ == '__main__':
diff --git a/tests/functional_tests/cold_signing.py b/tests/functional_tests/cold_signing.py
new file mode 100755
index 000000000..6895aec60
--- /dev/null
+++ b/tests/functional_tests/cold_signing.py
@@ -0,0 +1,146 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test cold tx signing
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class ColdSigningTest():
+ def run_test(self):
+ self.create(0)
+ self.mine()
+ self.transfer()
+
+ def create(self, idx):
+ print 'Creating hot and cold wallet'
+
+ self.hot_wallet = Wallet(idx = 0)
+ # close the wallet if any, will throw if none is loaded
+ try: self.hot_wallet.close_wallet()
+ except: pass
+
+ self.cold_wallet = Wallet(idx = 1)
+ # close the wallet if any, will throw if none is loaded
+ try: self.cold_wallet.close_wallet()
+ except: pass
+
+ seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
+ res = self.cold_wallet.restore_deterministic_wallet(seed = seed)
+ self.cold_wallet.set_daemon('127.0.0.1:11111', ssl_support = "disabled")
+ spend_key = self.cold_wallet.query_key("spend_key").key
+ view_key = self.cold_wallet.query_key("view_key").key
+ res = self.hot_wallet.generate_from_keys(viewkey = view_key, address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
+
+ ok = False
+ try: res = self.hot_wallet.query_key("spend_key")
+ except: ok = True
+ assert ok
+ ok = False
+ try: self.hot_wallet.query_key("mnemonic")
+ except: ok = True
+ assert ok
+ assert self.cold_wallet.query_key("view_key").key == view_key
+ assert self.cold_wallet.get_address().address == self.hot_wallet.get_address().address
+
+ def mine(self):
+ print("Mining some blocks")
+ daemon = Daemon()
+ wallet = Wallet()
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
+ wallet.refresh()
+
+ def transfer(self):
+ daemon = Daemon()
+
+ print("Creating transaction in hot wallet")
+
+ dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
+ payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'
+
+ res = self.hot_wallet.transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False)
+ assert len(res.tx_hash) == 32*2
+ txid = res.tx_hash
+ assert len(res.tx_key) == 0
+ assert res.amount > 0
+ amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) == 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) == 0
+ assert len(res.unsigned_txset) > 0
+ unsigned_txset = res.unsigned_txset
+
+ print 'Signing transaction with cold wallet'
+ res = self.cold_wallet.sign_transfer(unsigned_txset)
+ assert len(res.signed_txset) > 0
+ signed_txset = res.signed_txset
+ assert len(res.tx_hash_list) == 1
+ txid = res.tx_hash_list[0]
+ assert len(txid) == 64
+
+ print 'Submitting transaction with hot wallet'
+ res = self.hot_wallet.submit_transfer(signed_txset)
+ assert len(res.tx_hash_list) > 0
+ assert res.tx_hash_list[0] == txid
+
+ res = self.hot_wallet.get_transfers()
+ assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 1
+ assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ self.hot_wallet.refresh()
+
+ res = self.hot_wallet.get_transfers()
+ assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0
+ assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1
+
+ res = self.hot_wallet.get_tx_key(txid)
+ assert len(res.tx_key) == 0 or res.tx_key == '01' + '0' * 62 # identity is used as placeholder
+ res = self.cold_wallet.get_tx_key(txid)
+ assert len(res.tx_key) == 64
+
+
+class Guard:
+ def __enter__(self):
+ for i in range(2):
+ Wallet(idx = i).auto_refresh(False)
+ def __exit__(self, exc_type, exc_value, traceback):
+ for i in range(2):
+ Wallet(idx = i).auto_refresh(True)
+
+if __name__ == '__main__':
+ with Guard() as guard:
+ cs = ColdSigningTest().run_test()
diff --git a/tests/functional_tests/daemon_info.py b/tests/functional_tests/daemon_info.py
new file mode 100755
index 000000000..bd3528c3f
--- /dev/null
+++ b/tests/functional_tests/daemon_info.py
@@ -0,0 +1,89 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Test daemon RPC calls
+
+Test the following RPCs:
+ - get_info
+ - hard_fork_info
+
+"""
+
+from framework.daemon import Daemon
+
+class DaemonGetInfoTest():
+ def run_test(self):
+ self._test_hardfork_info()
+ self._test_get_info()
+
+ def _test_hardfork_info(self):
+ print('Test hard_fork_info')
+
+ daemon = Daemon()
+ res = daemon.hard_fork_info()
+
+ # hard_fork version should be set at height 1
+ assert 'earliest_height' in res.keys()
+ #assert res['earliest_height'] == 1;
+ assert res.earliest_height == 1
+
+ def _test_get_info(self):
+ print('Test get_info')
+
+ daemon = Daemon()
+ res = daemon.get_info()
+
+ # difficulty should be set to 1 for this test
+ assert 'difficulty' in res.keys()
+ assert res.difficulty == 1;
+
+ # nettype should not be TESTNET
+ assert 'testnet' in res.keys()
+ assert res.testnet == False;
+
+ # nettype should not be STAGENET
+ assert 'stagenet' in res.keys()
+ assert res.stagenet == False;
+
+ # nettype should be FAKECHAIN
+ assert 'nettype' in res.keys()
+ assert res.nettype == "fakechain";
+
+ # free_space should be > 0
+ assert 'free_space' in res.keys()
+ assert res.free_space > 0
+
+ # height should be greater or equal to 1
+ assert 'height' in res.keys()
+ assert res.height >= 1
+
+
+if __name__ == '__main__':
+ DaemonGetInfoTest().run_test()
diff --git a/tests/functional_tests/functional_tests_rpc.py b/tests/functional_tests/functional_tests_rpc.py
new file mode 100755
index 000000000..f2fef7e95
--- /dev/null
+++ b/tests/functional_tests/functional_tests_rpc.py
@@ -0,0 +1,135 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import sys
+import time
+import subprocess
+from signal import SIGTERM
+import socket
+import string
+import os
+
+USAGE = 'usage: functional_tests_rpc.py <python> <srcdir> <builddir> [<tests-to-run> | all]'
+DEFAULT_TESTS = ['daemon_info', 'blockchain', 'wallet_address', 'integrated_address', 'mining', 'transfer', 'txpool', 'multisig', 'cold_signing', 'sign_message', 'proofs']
+try:
+ python = sys.argv[1]
+ srcdir = sys.argv[2]
+ builddir = sys.argv[3]
+except:
+ print(USAGE)
+ sys.exit(1)
+
+try:
+ sys.argv[4]
+except:
+ print(USAGE)
+ print('Available tests: ' + string.join(DEFAULT_TESTS, ', '))
+ print('Or run all with "all"')
+ sys.exit(0)
+
+try:
+ tests = sys.argv[4:]
+ if tests == ['all']:
+ tests = DEFAULT_TESTS
+except:
+ tests = DEFAULT_TESTS
+
+N_MONERODS = 1
+N_WALLETS = 4
+
+monerod_base = [builddir + "/bin/monerod", "--regtest", "--fixed-difficulty", "1", "--offline", "--no-igd", "--p2p-bind-port", "monerod_p2p_port", "--rpc-bind-port", "monerod_rpc_port", "--zmq-rpc-bind-port", "monerod_zmq_port", "--non-interactive", "--disable-dns-checkpoints", "--check-updates", "disabled", "--rpc-ssl", "disabled", "--log-level", "1"]
+wallet_base = [builddir + "/bin/monero-wallet-rpc", "--wallet-dir", builddir + "/functional-tests-directory", "--rpc-bind-port", "wallet_port", "--disable-rpc-login", "--rpc-ssl", "disabled", "--daemon-ssl", "disabled", "--daemon-port", "18180", "--log-level", "1"]
+
+command_lines = []
+processes = []
+outputs = []
+ports = []
+
+for i in range(N_MONERODS):
+ command_lines.append([str(18180+i) if x == "monerod_rpc_port" else str(18280+i) if x == "monerod_p2p_port" else str(18380+i) if x == "monerod_zmq_port" else x for x in monerod_base])
+ outputs.append(open(builddir + '/tests/functional_tests/monerod' + str(i) + '.log', 'a+'))
+ ports.append(18180+i)
+
+for i in range(N_WALLETS):
+ command_lines.append([str(18090+i) if x == "wallet_port" else x for x in wallet_base])
+ outputs.append(open(builddir + '/tests/functional_tests/wallet' + str(i) + '.log', 'a+'))
+ ports.append(18090+i)
+
+print('Starting servers...')
+try:
+ PYTHONPATH = os.environ['PYTHONPATH'] if 'PYTHONPATH' in os.environ else ''
+ if len(PYTHONPATH) > 0:
+ PYTHONPATH += ':'
+ PYTHONPATH += srcdir + '/../../utils/python-rpc'
+ os.environ['PYTHONPATH'] = PYTHONPATH
+ for i in range(len(command_lines)):
+ #print('Running: ' + str(command_lines[i]))
+ processes.append(subprocess.Popen(command_lines[i], stdout = outputs[i]))
+except Exception, e:
+ print('Error: ' + str(e))
+ sys.exit(1)
+
+def kill():
+ for i in range(len(processes)):
+ try: processes[i].send_signal(SIGTERM)
+ except: pass
+
+# wait for error/startup
+for i in range(10):
+ time.sleep(1)
+ all_open = True
+ for port in ports:
+ s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+ s.settimeout(1)
+ if s.connect_ex(('127.0.0.1', port)) != 0:
+ all_open = False
+ break
+ s.close()
+ if all_open:
+ break
+
+if not all_open:
+ print('Failed to start wallet or daemon')
+ kill()
+ sys.exit(1)
+
+PASS = []
+FAIL = []
+for test in tests:
+ try:
+ print('[TEST STARTED] ' + test)
+ cmd = [python, srcdir + '/' + test + ".py"]
+ subprocess.check_call(cmd)
+ PASS.append(test)
+ print('[TEST PASSED] ' + test)
+ except:
+ FAIL.append(test)
+ print('[TEST FAILED] ' + test)
+ pass
+
+print('Stopping servers...')
+kill()
+
+# wait for exit, the poll method does not work (https://bugs.python.org/issue2475) so we wait, possibly forever if the process hangs
+if True:
+ for p in processes:
+ p.wait()
+else:
+ for i in range(10):
+ n_returncode = 0
+ for p in processes:
+ p.poll()
+ if p.returncode:
+ n_returncode += 1
+ if n_returncode == len(processes):
+ print('All done: ' + string.join([x.returncode for x in processes], ', '))
+ break
+ time.sleep(1)
+ for p in processes:
+ if not p.returncode:
+ print('Failed to stop process')
+
+if len(FAIL) == 0:
+ print('Done, ' + str(len(PASS)) + '/' + str(len(tests)) + ' tests passed')
+else:
+ print('Done, ' + str(len(FAIL)) + '/' + str(len(tests)) + ' tests failed: ' + string.join(FAIL, ', '))
diff --git a/tests/functional_tests/integrated_address.py b/tests/functional_tests/integrated_address.py
new file mode 100755
index 000000000..338dd14ae
--- /dev/null
+++ b/tests/functional_tests/integrated_address.py
@@ -0,0 +1,101 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test integrated address RPC calls
+
+Test the following RPCs:
+ - make_integrated_address
+ - split_integrated_address
+
+"""
+
+from framework.wallet import Wallet
+
+class IntegratedAddressTest():
+ def run_test(self):
+ self.create()
+ self.check()
+
+ def create(self):
+ print 'Creating wallet'
+ wallet = Wallet()
+ # close the wallet if any, will throw if none is loaded
+ try: wallet.close_wallet()
+ except: pass
+ seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
+ res = wallet.restore_deterministic_wallet(seed = seed)
+ assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert res.seed == seed
+
+ def check(self):
+ wallet = Wallet()
+
+ print 'Checking local address'
+ res = wallet.make_integrated_address(payment_id = '0123456789abcdef')
+ assert res.integrated_address == '4CMe2PUhs4J4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfSbLRB61BQVATzerHGj'
+ assert res.payment_id == '0123456789abcdef'
+ res = wallet.split_integrated_address(res.integrated_address)
+ assert res.standard_address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert res.payment_id == '0123456789abcdef'
+
+ print 'Checking different address'
+ res = wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788')
+ assert res.integrated_address == '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo'
+ assert res.payment_id == '1122334455667788'
+ res = wallet.split_integrated_address(res.integrated_address)
+ assert res.standard_address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK'
+ assert res.payment_id == '1122334455667788'
+
+ print 'Checking bad payment id'
+ fails = 0
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '11223344556677880')
+ except: fails += 1
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778')
+ except: fails += 1
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '')
+ except: fails += 1
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '112233445566778g')
+ except: fails += 1
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', payment_id = '1122334455667788112233445566778811223344556677881122334455667788')
+ except: fails += 1
+ assert fails == 5
+
+ print 'Checking bad standard address'
+ fails = 0
+ try: wallet.make_integrated_address(standard_address = '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerr', payment_id = '1122334455667788')
+ except: fails += 1
+ try: wallet.make_integrated_address(standard_address = '4GYjoMG9Y2BBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCVSs1ZojwrDCGS5rUuo', payment_id = '1122334455667788')
+ except: fails += 1
+ assert fails == 2
+
+if __name__ == '__main__':
+ IntegratedAddressTest().run_test()
diff --git a/tests/functional_tests/mining.py b/tests/functional_tests/mining.py
new file mode 100755
index 000000000..1b189beb2
--- /dev/null
+++ b/tests/functional_tests/mining.py
@@ -0,0 +1,123 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2018 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test daemon mining RPC calls
+
+Test the following RPCs:
+ - start_mining
+ - stop_mining
+ - mining_status
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class MiningTest():
+ def run_test(self):
+ self.create()
+ self.mine()
+
+ def create(self):
+ print 'Creating wallet'
+ wallet = Wallet()
+ # close the wallet if any, will throw if none is loaded
+ try: wallet.close_wallet()
+ except: pass
+ res = wallet.restore_deterministic_wallet(seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted')
+
+ def mine(self):
+ print "Test mining"
+
+ daemon = Daemon()
+ wallet = Wallet()
+
+ # check info/height/balance before generating blocks
+ res_info = daemon.get_info()
+ prev_height = res_info.height
+ res_getbalance = wallet.get_balance()
+ prev_balance = res_getbalance.balance
+
+ res_status = daemon.mining_status()
+
+ res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1)
+
+ res_status = daemon.mining_status()
+ assert res_status.active == True
+ assert res_status.threads_count == 1
+ assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert res_status.is_background_mining_enabled == False
+ assert res_status.block_reward >= 600000000000
+
+ # wait till we mined a few of them
+ timeout = 5
+ timeout_height = prev_height
+ while True:
+ time.sleep(1)
+ res_info = daemon.get_info()
+ height = res_info.height
+ if height >= prev_height + 5:
+ break
+ if height > timeout_height:
+ timeout = 5
+ timeout_height = height
+ else:
+ timeout -= 1
+ assert timeout >= 0
+
+ res = daemon.stop_mining()
+ res_status = daemon.mining_status()
+ assert res_status.active == False
+
+ res_info = daemon.get_info()
+ new_height = res_info.height
+
+ wallet.refresh()
+ res_getbalance = wallet.get_balance()
+ balance = res_getbalance.balance
+ assert balance >= prev_balance + (new_height - prev_height) * 600000000000
+
+ res = daemon.start_mining('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', threads_count = 1, do_background_mining = True)
+ res_status = daemon.mining_status()
+ assert res_status.active == True
+ assert res_status.threads_count == 1
+ assert res_status.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert res_status.is_background_mining_enabled == True
+ assert res_status.block_reward >= 600000000000
+
+ # don't wait, might be a while if the machine is busy, which it probably is
+ res = daemon.stop_mining()
+ res_status = daemon.mining_status()
+ assert res_status.active == False
+
+
+if __name__ == '__main__':
+ MiningTest().run_test()
diff --git a/tests/functional_tests/multisig.py b/tests/functional_tests/multisig.py
new file mode 100755
index 000000000..a0e8551cd
--- /dev/null
+++ b/tests/functional_tests/multisig.py
@@ -0,0 +1,227 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test multisig transfers
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class MultisigTest():
+ def run_test(self):
+ self.mine('493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk', 5)
+ self.mine('42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y', 5)
+ self.mine('47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53', 5)
+ self.mine('44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR', 5)
+ self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 60)
+
+ self.create_multisig_wallets(2, 2, '493DsrfJPqiN3Suv9RcRDoZEbQtKZX1sNcGPA3GhkKYEEmivk8kjQrTdRdVc4ZbmzWJuE157z9NNUKmF2VDfdYDR3CziGMk')
+ self.import_multisig_info([1, 0], 5)
+ txid = self.transfer([1, 0])
+ self.import_multisig_info([0, 1], 6)
+ self.check_transaction(txid)
+
+ self.create_multisig_wallets(2, 3, '42jSRGmmKN96V2j3B8X2DbiNThBXW1tSi1rW1uwkqbyURenq3eC3yosNm8HEMdHuWwKMFGzMUB3RCTvcTaW9kHpdRPP7p5y')
+ self.import_multisig_info([0, 2], 5)
+ txid = self.transfer([0, 2])
+ self.import_multisig_info([0, 1, 2], 6)
+ self.check_transaction(txid)
+
+ self.create_multisig_wallets(3, 4, '47fF32AdrmXG84FcPY697uZdd42pMMGiH5UpiTRTt3YX2pZC7t7wkzEMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUgxRd53')
+ self.import_multisig_info([0, 2, 3], 5)
+ txid = self.transfer([0, 2, 3])
+ self.import_multisig_info([0, 1, 2, 3], 6)
+ self.check_transaction(txid)
+
+ self.create_multisig_wallets(2, 4, '44SKxxLQw929wRF6BA9paQ1EWFshNnKhXM3qz6Mo3JGDE2YG3xyzVutMStEicxbQGRfrYvAAYxH6Fe8rnD56EaNwUiqhcwR')
+ self.import_multisig_info([1, 2], 5)
+ txid = self.transfer([1, 2])
+ self.import_multisig_info([0, 1, 2, 3], 6)
+ self.check_transaction(txid)
+
+ def mine(self, address, blocks):
+ print("Mining some blocks")
+ daemon = Daemon()
+ daemon.generateblocks(address, blocks)
+
+ def create_multisig_wallets(self, M_threshold, N_total, expected_address):
+ print('Creating ' + str(M_threshold) + '/' + str(N_total) + ' multisig wallet')
+ seeds = [
+ 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
+ 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
+ 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid',
+ 'waking gown buffet negative reorder speedy baffles hotel pliers dewdrop actress diplomat lymph emit ajar mailed kennel cynical jaunt justice weavers height teardrop toyed lymph',
+ ]
+ assert M_threshold <= N_total
+ assert N_total <= len(seeds)
+ self.wallet = [None] * N_total
+ info = []
+ for i in range(N_total):
+ self.wallet[i] = Wallet(idx = i)
+ try: self.wallet[i].close_wallet()
+ except: pass
+ res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
+ res = self.wallet[i].prepare_multisig()
+ assert len(res.multisig_info) > 0
+ info.append(res.multisig_info)
+
+ for i in range(N_total):
+ res = self.wallet[i].is_multisig()
+ assert res.multisig == False
+
+ addresses = []
+ next_stage = []
+ for i in range(N_total):
+ res = self.wallet[i].make_multisig(info, M_threshold)
+ addresses.append(res.address)
+ next_stage.append(res.multisig_info)
+
+ for i in range(N_total):
+ res = self.wallet[i].is_multisig()
+ assert res.multisig == True
+ assert res.ready == (M_threshold == N_total)
+ assert res.threshold == M_threshold
+ assert res.total == N_total
+
+ while True:
+ n_empty = 0
+ for i in range(len(next_stage)):
+ if len(next_stage[i]) == 0:
+ n_empty += 1
+ assert n_empty == 0 or n_empty == len(next_stage)
+ if n_empty == len(next_stage):
+ break
+ info = next_stage
+ next_stage = []
+ addresses = []
+ for i in range(N_total):
+ res = self.wallet[i].exchange_multisig_keys(info)
+ next_stage.append(res.multisig_info)
+ addresses.append(res.address)
+ for i in range(N_total):
+ assert addresses[i] == expected_address
+
+ for i in range(N_total):
+ res = self.wallet[i].is_multisig()
+ assert res.multisig == True
+ assert res.ready == True
+ assert res.threshold == M_threshold
+ assert res.total == N_total
+
+
+ def import_multisig_info(self, signers, expected_outputs):
+ assert len(signers) >= 2
+
+ print('Importing multisig info from ' + str(signers))
+
+ info = []
+ for i in signers:
+ self.wallet[i].refresh()
+ res = self.wallet[i].export_multisig_info()
+ assert len(res.info) > 0
+ info.append(res.info)
+ for i in signers:
+ res = self.wallet[i].import_multisig_info(info)
+ assert res.n_outputs == expected_outputs
+
+ def transfer(self, signers):
+ assert len(signers) >= 2
+
+ daemon = Daemon()
+
+ print("Creating multisig transaction from wallet " + str(signers[0]))
+
+ dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
+ res = self.wallet[signers[0]].transfer([dst])
+ assert len(res.tx_hash) == 0 # not known yet
+ txid = res.tx_hash
+ assert len(res.tx_key) == 32*2
+ assert res.amount > 0
+ amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) == 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) > 0
+ assert len(res.unsigned_txset) == 0
+ multisig_txset = res.multisig_txset
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ for i in range(len(self.wallet)):
+ self.wallet[i].refresh()
+
+ for i in range(len(signers[1:])):
+ print('Signing multisig transaction with wallet ' + str(signers[i+1]))
+ res = self.wallet[signers[i+1]].sign_multisig(multisig_txset)
+ multisig_txset = res.tx_data_hex
+ assert len(res.tx_hash_list if 'tx_hash_list' in res else []) == (i == len(signers[1:]) - 1)
+
+ if i < len(signers[1:]) - 1:
+ print('Submitting multisig transaction prematurely with wallet ' + str(signers[-1]))
+ ok = False
+ try: self.wallet[signers[-1]].submit_multisig(multisig_txset)
+ except: ok = True
+ assert ok
+
+ print('Submitting multisig transaction with wallet ' + str(signers[-1]))
+ res = self.wallet[signers[-1]].submit_multisig(multisig_txset)
+ assert len(res.tx_hash_list) == 1
+ txid = res.tx_hash_list[0]
+
+ for i in range(len(self.wallet)):
+ self.wallet[i].refresh()
+ res = self.wallet[i].get_transfers()
+ assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == (1 if i == signers[-1] else 0)
+ assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 0
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ return txid
+
+ def check_transaction(self, txid):
+ for i in range(len(self.wallet)):
+ self.wallet[i].refresh()
+ res = self.wallet[i].get_transfers()
+ assert len([x for x in (res['pending'] if 'pending' in res else []) if x.txid == txid]) == 0
+ assert len([x for x in (res['out'] if 'out' in res else []) if x.txid == txid]) == 1
+
+
+class Guard:
+ def __enter__(self):
+ for i in range(4):
+ Wallet(idx = i).auto_refresh(False)
+ def __exit__(self, exc_type, exc_value, traceback):
+ for i in range(4):
+ Wallet(idx = i).auto_refresh(True)
+
+if __name__ == '__main__':
+ with Guard() as guard:
+ MultisigTest().run_test()
diff --git a/tests/functional_tests/proofs.py b/tests/functional_tests/proofs.py
new file mode 100755
index 000000000..0a0b6304d
--- /dev/null
+++ b/tests/functional_tests/proofs.py
@@ -0,0 +1,282 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test misc proofs (tx key, send, receive, reserve)
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class ProofsTest():
+ def run_test(self):
+ self.mine('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
+ self.create_wallets()
+ txid, tx_key, amount = self.transfer()
+ self.check_tx_key(txid, tx_key, amount)
+ self.check_tx_proof(txid, amount)
+ self.check_reserve_proof()
+
+ def mine(self, address, blocks):
+ print("Mining some blocks")
+ daemon = Daemon()
+ daemon.generateblocks(address, blocks)
+
+ def transfer(self):
+ print('Creating transaction')
+ self.wallet[0].refresh()
+ dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount':123456789000}
+ res = self.wallet[0].transfer([dst], get_tx_key = True)
+ assert len(res.tx_hash) == 64
+ assert len(res.tx_key) == 64
+ daemon = Daemon()
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ return (res.tx_hash, res.tx_key, 123456789000)
+
+ def create_wallets(self):
+ print('Creating wallets')
+ seeds = [
+ 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
+ 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
+ ]
+ self.wallet = [None, None]
+ for i in range(2):
+ self.wallet[i] = Wallet(idx = i)
+ try: self.wallet[i].close_wallet()
+ except: pass
+ res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
+
+ def check_tx_key(self, txid, tx_key, amount):
+ daemon = Daemon()
+
+ print('Checking tx key')
+
+ self.wallet[0].refresh()
+ self.wallet[1].refresh()
+
+ sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+ res = self.wallet[0].get_tx_key(txid)
+ assert res.tx_key == tx_key
+ res = self.wallet[0].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address)
+ assert res.received == amount
+ assert not res.in_pool
+ assert res.confirmations == 1
+ res = self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = receiving_address)
+ assert res.received == amount
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ self.wallet[1].check_tx_key(txid = txid, tx_key = tx_key, address = sending_address)
+ assert res.received >= 0 # might be change
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ ok = False
+ try: self.wallet[1].check_tx_key(txid = '0' * 64, tx_key = tx_key, address = receiving_address)
+ except: ok = True
+ assert ok
+
+ res = self.wallet[1].check_tx_key(txid = txid, tx_key = '0' * 64, address = receiving_address)
+ assert res.received == 0
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ def check_tx_proof(self, txid, amount):
+ daemon = Daemon()
+
+ print('Checking tx proof')
+
+ self.wallet[0].refresh()
+ self.wallet[1].refresh()
+
+ sending_address = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ receiving_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+ res = self.wallet[0].get_tx_proof(txid, sending_address, 'foo');
+ assert res.signature.startswith('InProof');
+ signature0i = res.signature
+ res = self.wallet[0].get_tx_proof(txid, receiving_address, 'bar');
+ assert res.signature.startswith('OutProof');
+ signature0o = res.signature
+ res = self.wallet[1].get_tx_proof(txid, receiving_address, 'baz');
+ assert res.signature.startswith('InProof');
+ signature1 = res.signature
+
+ res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature0i);
+ assert res.good
+ assert res.received > 0 # likely change
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof('0' * 64, sending_address, 'foo', signature0i);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'foo', signature0i);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, sending_address, '', signature0i);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'foo', signature1);
+ except: ok = True
+ assert ok or not res.good
+
+
+ res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0o);
+ assert res.good
+ assert res.received == amount
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof('0' * 64, receiving_address, 'bar', signature0o);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, sending_address, 'bar', signature0o);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, receiving_address, '', signature0o);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[0].check_tx_proof(txid, receiving_address, 'bar', signature0i);
+ except: ok = True
+ assert ok or not res.good
+
+
+ res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature1);
+ assert res.good
+ assert res.received == amount
+ assert not res.in_pool
+ assert res.confirmations == 1
+
+ ok = False
+ try: res = self.wallet[1].check_tx_proof('0' * 64, receiving_address, 'baz', signature1);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[1].check_tx_proof(txid, sending_address, 'baz', signature1);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[1].check_tx_proof(txid, receiving_address, '', signature1);
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[1].check_tx_proof(txid, receiving_address, 'baz', signature0o);
+ except: ok = True
+ assert ok or not res.good
+
+
+ def check_reserve_proof(self):
+ daemon = Daemon()
+
+ print('Checking reserve proof')
+
+ address0 = '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ address1 = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+
+ self.wallet[0].refresh()
+ res = self.wallet[0].get_balance()
+ balance0 = res.balance
+ self.wallet[1].refresh()
+ res = self.wallet[1].get_balance()
+ balance1 = res.balance
+
+ res = self.wallet[0].get_reserve_proof(all_ = True, message = 'foo')
+ assert res.signature.startswith('ReserveProof')
+ signature = res.signature
+ for i in range(2):
+ res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
+ assert res.good
+ assert res.total == balance0
+
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature)
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature)
+ except: ok = True
+ assert ok or not res.good
+
+ amount = int(balance0 / 10)
+ res = self.wallet[0].get_reserve_proof(all_ = False, amount = amount, message = 'foo')
+ assert res.signature.startswith('ReserveProof')
+ signature = res.signature
+ for i in range(2):
+ res = self.wallet[i].check_reserve_proof(address = address0, message = 'foo', signature = signature)
+ assert res.good
+ assert res.total >= amount and res.total <= balance0
+
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address0, message = 'bar', signature = signature)
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: res = self.wallet[i].check_reserve_proof(address = address1, message = 'foo', signature = signature)
+ except: ok = True
+ assert ok or not res.good
+
+ ok = False
+ try: self.wallet[0].get_reserve_proof(all_ = False, amount = balance0 + 1, message = 'foo')
+ except: ok = True
+ assert ok
+
+
+class Guard:
+ def __enter__(self):
+ for i in range(4):
+ Wallet(idx = i).auto_refresh(False)
+ def __exit__(self, exc_type, exc_value, traceback):
+ for i in range(4):
+ Wallet(idx = i).auto_refresh(True)
+
+if __name__ == '__main__':
+ with Guard() as guard:
+ ProofsTest().run_test()
diff --git a/tests/functional_tests/sign_message.py b/tests/functional_tests/sign_message.py
new file mode 100755
index 000000000..4c3ec3588
--- /dev/null
+++ b/tests/functional_tests/sign_message.py
@@ -0,0 +1,85 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test message signing/verification RPC calls
+
+Test the following RPCs:
+ - sign
+ - verify
+
+"""
+
+from framework.wallet import Wallet
+
+class MessageSigningTest():
+ def run_test(self):
+ self.create()
+ self.check_signing()
+
+ def create(self):
+ print 'Creating wallets'
+ seeds = [
+ 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
+ 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
+ ]
+ self.address = [
+ '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm',
+ '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW',
+ ]
+ self.wallet = [None, None]
+ for i in range(2):
+ self.wallet[i] = Wallet(idx = i)
+ # close the wallet if any, will throw if none is loaded
+ try: self.wallet[i].close_wallet()
+ except: pass
+ res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
+ assert res.address == self.address[i]
+ assert res.seed == seeds[i]
+
+ def check_signing(self):
+ print 'Signing/verifing messages'
+ messages = ['foo', '']
+ for message in messages:
+ res = self.wallet[0].sign(message)
+ signature = res.signature
+ for i in range(2):
+ res = self.wallet[i].verify(message, self.address[0], signature)
+ assert res.good
+ res = self.wallet[i].verify('different', self.address[0], signature)
+ assert not res.good
+ res = self.wallet[i].verify(message, self.address[1], signature)
+ assert not res.good
+ res = self.wallet[i].verify(message, self.address[0], signature + 'x')
+ assert not res.good
+
+if __name__ == '__main__':
+ MessageSigningTest().run_test()
diff --git a/tests/functional_tests/speed.py b/tests/functional_tests/speed.py
index 3d2af9a10..bd8892df8 100755
--- a/tests/functional_tests/speed.py
+++ b/tests/functional_tests/speed.py
@@ -42,8 +42,8 @@ import time
from time import sleep
from decimal import Decimal
-from test_framework.daemon import Daemon
-from test_framework.wallet import Wallet
+from framework.daemon import Daemon
+from framework.wallet import Wallet
class SpeedTest():
@@ -58,7 +58,7 @@ class SpeedTest():
self._test_speed_generateblocks(daemon=daemon, blocks=70)
for i in range(1, 10):
- while wallet.get_balance()['unlocked_balance'] == 0:
+ while wallet.get_balance().unlocked_balance == 0:
print('Waiting for wallet to refresh...')
sleep(1)
self._test_speed_transfer_split(wallet=wallet)
diff --git a/tests/functional_tests/test_framework/daemon.py b/tests/functional_tests/test_framework/daemon.py
deleted file mode 100644
index f3490b232..000000000
--- a/tests/functional_tests/test_framework/daemon.py
+++ /dev/null
@@ -1,105 +0,0 @@
-# Copyright (c) 2018 The Monero Project
-#
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification, are
-# permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice, this list of
-# conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice, this list
-# of conditions and the following disclaimer in the documentation and/or other
-# materials provided with the distribution.
-#
-# 3. Neither the name of the copyright holder nor the names of its contributors may be
-# used to endorse or promote products derived from this software without specific
-# prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
-# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
-# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""Daemon class to make rpc calls and store state."""
-
-from .rpc import JSONRPC
-
-class Daemon(object):
-
- def __init__(self, protocol='http', host='127.0.0.1', port=18081, path='/json_rpc'):
- self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
-
- def getblocktemplate(self, address):
- getblocktemplate = {
- 'method': 'getblocktemplate',
- 'params': {
- 'wallet_address': address,
- 'reserve_size' : 1
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(getblocktemplate)
-
- def submitblock(self, block):
- submitblock = {
- 'method': 'submitblock',
- 'params': [ block ],
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(submitblock)
-
- def getblock(self, height=0):
- getblock = {
- 'method': 'getblock',
- 'params': {
- 'height': height
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(getblock)
-
- def get_connections(self):
- get_connections = {
- 'method': 'get_connections',
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(get_connections)
-
- def get_info(self):
- get_info = {
- 'method': 'get_info',
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(get_info)
-
- def hard_fork_info(self):
- hard_fork_info = {
- 'method': 'hard_fork_info',
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(hard_fork_info)
-
- def generateblocks(self, address, blocks=1):
- generateblocks = {
- 'method': 'generateblocks',
- 'params': {
- 'amount_of_blocks' : blocks,
- 'reserve_size' : 20,
- 'wallet_address': address
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(generateblocks)
diff --git a/tests/functional_tests/test_framework/wallet.py b/tests/functional_tests/test_framework/wallet.py
deleted file mode 100644
index 357eab5b2..000000000
--- a/tests/functional_tests/test_framework/wallet.py
+++ /dev/null
@@ -1,120 +0,0 @@
-# Copyright (c) 2018 The Monero Project
-#
-# All rights reserved.
-#
-# Redistribution and use in source and binary forms, with or without modification, are
-# permitted provided that the following conditions are met:
-#
-# 1. Redistributions of source code must retain the above copyright notice, this list of
-# conditions and the following disclaimer.
-#
-# 2. Redistributions in binary form must reproduce the above copyright notice, this list
-# of conditions and the following disclaimer in the documentation and/or other
-# materials provided with the distribution.
-#
-# 3. Neither the name of the copyright holder nor the names of its contributors may be
-# used to endorse or promote products derived from this software without specific
-# prior written permission.
-#
-# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
-# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
-# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
-# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
-# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
-# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
-# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
-# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
-# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-
-"""Daemon class to make rpc calls and store state."""
-
-from .rpc import JSONRPC
-
-class Wallet(object):
-
- def __init__(self, protocol='http', host='127.0.0.1', port=18083, path='/json_rpc'):
- self.rpc = JSONRPC('{protocol}://{host}:{port}{path}'.format(protocol=protocol, host=host, port=port, path=path))
-
- def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1):
- destinations = []
- for i in range(transfer_number_of_destinations):
- destinations.append({"amount":transfer_amount,"address":address})
- return destinations
-
- def make_destinations(self, addresses, transfer_amounts):
- destinations = []
- for i in range(len(addresses)):
- destinations.append({'amount':transfer_amounts[i],'address':addresses[i]})
- return destinations
-
- def transfer(self, destinations, ringsize=7, payment_id=''):
- transfer = {
- 'method': 'transfer',
- 'params': {
- 'destinations': destinations,
- 'mixin' : ringsize - 1,
- 'get_tx_key' : True
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- if(len(payment_id) > 0):
- transfer['params'].update({'payment_id' : payment_id})
- return self.rpc.send_request(transfer)
-
- def transfer_split(self, destinations, ringsize=7, payment_id=''):
- print(destinations)
- transfer = {
- "method": "transfer_split",
- "params": {
- "destinations": destinations,
- "mixin" : ringsize - 1,
- "get_tx_key" : True,
- "new_algorithm" : True
- },
- "jsonrpc": "2.0",
- "id": "0"
- }
- if(len(payment_id) > 0):
- transfer['params'].update({'payment_id' : payment_id})
- return self.rpc.send_request(transfer)
-
- def create_wallet(self, index=''):
- create_wallet = {
- 'method': 'create_wallet',
- 'params': {
- 'filename': 'testWallet' + index,
- 'password' : '',
- 'language' : 'English'
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(create_wallet)
-
- def get_balance(self):
- get_balance = {
- 'method': 'get_balance',
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(get_balance)
-
- def sweep_dust(self):
- sweep_dust = {
- 'method': 'sweep_dust',
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(sweep_dust)
-
- def sweep_all(self, address):
- sweep_all = {
- 'method': 'sweep_all',
- 'params' : {
- 'address' : ''
- },
- 'jsonrpc': '2.0',
- 'id': '0'
- }
- return self.rpc.send_request(sweep_all)
diff --git a/tests/functional_tests/transfer.py b/tests/functional_tests/transfer.py
new file mode 100755
index 000000000..b7a85f1d6
--- /dev/null
+++ b/tests/functional_tests/transfer.py
@@ -0,0 +1,487 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test simple transfers
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class TransferTest():
+ def run_test(self):
+ self.create()
+ self.mine()
+ self.transfer()
+ self.check_get_bulk_payments()
+
+ def create(self):
+ print 'Creating wallets'
+ seeds = [
+ 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted',
+ 'peeled mixture ionic radar utopia puddle buying illness nuns gadget river spout cavernous bounced paradise drunk looking cottage jump tequila melting went winter adjust spout',
+ 'dilute gutter certain antics pamphlet macro enjoy left slid guarded bogeys upload nineteen bomb jubilee enhanced irritate turnip eggs swung jukebox loudly reduce sedan slid',
+ ]
+ self.wallet = [None] * len(seeds)
+ for i in range(len(seeds)):
+ self.wallet[i] = Wallet(idx = i)
+ # close the wallet if any, will throw if none is loaded
+ try: self.wallet[i].close_wallet()
+ except: pass
+ res = self.wallet[i].restore_deterministic_wallet(seed = seeds[i])
+
+ def mine(self):
+ print("Mining some blocks")
+ daemon = Daemon()
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
+ for i in range(len(self.wallet)):
+ self.wallet[i].refresh()
+
+ def transfer(self):
+ daemon = Daemon()
+
+ print("Creating transfer to self")
+
+ dst = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
+ payment_id = '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'
+
+ start_balances = [0] * len(self.wallet)
+ running_balances = [0] * len(self.wallet)
+ for i in range(len(self.wallet)):
+ res = self.wallet[i].get_balance()
+ start_balances[i] = res.balance
+ running_balances[i] = res.balance
+ assert res.unlocked_balance <= res.balance
+ if i == 0:
+ assert res.blocks_to_unlock == 59 # we've been mining to it
+ else:
+ assert res.blocks_to_unlock == 0
+
+ print ('Checking short payment IDs cannot be used when not in an integrated address')
+ ok = False
+ try: self.wallet[0].transfer([dst], ring_size = 11, payment_id = '1234567812345678', get_tx_key = False)
+ except: ok = True
+ assert ok
+
+ print ('Checking empty destination is rejected')
+ ok = False
+ try: self.wallet[0].transfer([], ring_size = 11, get_tx_key = False)
+ except: ok = True
+ assert ok
+
+ res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = False)
+ assert len(res.tx_hash) == 32*2
+ txid = res.tx_hash
+ assert len(res.tx_key) == 0
+ assert res.amount > 0
+ amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) == 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) == 0
+ assert len(res.unsigned_txset) == 0
+ unsigned_txset = res.unsigned_txset
+
+ self.wallet[0].refresh()
+
+ res = daemon.get_info()
+ height = res.height
+
+ res = self.wallet[0].get_transfers()
+ assert len(res['in']) == height - 1 # coinbases
+ assert not 'out' in res or len(res.out) == 0 # not mined yet
+ assert len(res.pending) == 1
+ assert not 'pool' in res or len(res.pool) == 0
+ assert not 'failed' in res or len(res.failed) == 0
+ for e in res['in']:
+ assert e.type == 'block'
+ e = res.pending[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'pending'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 0
+
+ running_balances[0] -= 1000000000000 + fee
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.getlastblockheader()
+ running_balances[0] += res.block_header.reward
+ self.wallet[0].refresh()
+
+ running_balances[0] += 1000000000000
+
+ res = self.wallet[0].get_transfers()
+ assert len(res['in']) == height # coinbases
+ assert len(res.out) == 1 # not mined yet
+ assert not 'pending' in res or len(res.pending) == 0
+ assert not 'pool' in res or len(res.pool) == 0
+ assert not 'failed' in res or len(res.failed) == 0
+ for e in res['in']:
+ assert e.type == 'block'
+ e = res.out[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'out'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 1
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ print("Creating transfer to another, manual relay")
+
+ dst = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1000000000000}
+ res = self.wallet[0].transfer([dst], ring_size = 11, payment_id = payment_id, get_tx_key = True, do_not_relay = True, get_tx_hex = True)
+ assert len(res.tx_hash) == 32*2
+ txid = res.tx_hash
+ assert len(res.tx_key) == 32*2
+ assert res.amount == 1000000000000
+ amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) > 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) == 0
+ assert len(res.unsigned_txset) == 0
+ tx_blob = res.tx_blob
+
+ res = daemon.send_raw_transaction(tx_blob)
+ assert res.not_relayed == False
+ assert res.low_mixin == False
+ assert res.double_spend == False
+ assert res.invalid_input == False
+ assert res.invalid_output == False
+ assert res.too_big == False
+ assert res.overspend == False
+ assert res.fee_too_low == False
+ assert res.not_rct == False
+
+ self.wallet[0].refresh()
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ self.wallet[1].refresh()
+
+ res = self.wallet[1].get_transfers()
+ assert not 'in' in res or len(res['in']) == 0
+ assert not 'out' in res or len(res.out) == 0
+ assert not 'pending' in res or len(res.pending) == 0
+ assert len(res.pool) == 1
+ assert not 'failed' in res or len(res.failed) == 0
+ e = res.pool[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'pool'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 0
+ assert e.amount == amount
+ assert e.fee == fee
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.getlastblockheader()
+ running_balances[0] -= 1000000000000 + fee
+ running_balances[0] += res.block_header.reward
+ self.wallet[1].refresh()
+ running_balances[1] += 1000000000000
+
+ res = self.wallet[1].get_transfers()
+ assert len(res['in']) == 1
+ assert not 'out' in res or len(res.out) == 0
+ assert not 'pending' in res or len(res.pending) == 0
+ assert not 'pool' in res or len(res.pool) == 0
+ assert not 'failed' in res or len(res.failed) == 0
+ e = res['in'][0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'in'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 1
+ assert e.amount == amount
+ assert e.fee == fee
+
+ res = self.wallet[1].get_balance()
+ assert res.balance == running_balances[1]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 9
+
+ print 'Creating multi out transfer'
+
+ self.wallet[0].refresh()
+
+ dst0 = {'address': '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 'amount': 1000000000000}
+ dst1 = {'address': '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', 'amount': 1100000000000}
+ dst2 = {'address': '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 'amount': 1200000000000}
+ res = self.wallet[0].transfer([dst0, dst1, dst2], ring_size = 11, payment_id = payment_id, get_tx_key = True)
+ assert len(res.tx_hash) == 32*2
+ txid = res.tx_hash
+ assert len(res.tx_key) == 32*2
+ assert res.amount == 1000000000000 + 1100000000000 + 1200000000000
+ amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) == 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) == 0
+ assert len(res.unsigned_txset) == 0
+ unsigned_txset = res.unsigned_txset
+
+ running_balances[0] -= 1000000000000 + 1100000000000 + 1200000000000 + fee
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.getlastblockheader()
+ running_balances[0] += res.block_header.reward
+ running_balances[0] += 1000000000000
+ running_balances[1] += 1100000000000
+ running_balances[2] += 1200000000000
+ self.wallet[0].refresh()
+
+ res = self.wallet[0].get_transfers()
+ assert len(res['in']) == height + 2
+ assert len(res.out) == 3
+ assert not 'pending' in res or len(res.pending) == 0
+ assert not 'pool' in res or len(res.pool) == 1
+ assert not 'failed' in res or len(res.failed) == 0
+ e = [o for o in res.out if o.txid == txid]
+ assert len(e) == 1
+ e = e[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'out'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 1
+
+ assert e.amount == amount
+ assert e.fee == fee
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ self.wallet[1].refresh()
+ res = self.wallet[1].get_transfers()
+ assert len(res['in']) == 2
+ assert not 'out' in res or len(res.out) == 0
+ assert not 'pending' in res or len(res.pending) == 0
+ assert not 'pool' in res or len(res.pool) == 0
+ assert not 'failed' in res or len(res.failed) == 0
+ e = [o for o in res['in'] if o.txid == txid]
+ assert len(e) == 1
+ e = e[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'in'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 1
+ assert e.amount == 1100000000000
+ assert e.fee == fee
+
+ res = self.wallet[1].get_balance()
+ assert res.balance == running_balances[1]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 9
+
+ self.wallet[2].refresh()
+ res = self.wallet[2].get_transfers()
+ assert len(res['in']) == 1
+ assert not 'out' in res or len(res.out) == 0
+ assert not 'pending' in res or len(res.pending) == 0
+ assert not 'pool' in res or len(res.pool) == 0
+ assert not 'failed' in res or len(res.failed) == 0
+ e = [o for o in res['in'] if o.txid == txid]
+ assert len(e) == 1
+ e = e[0]
+ assert e.txid == txid
+ assert e.payment_id == payment_id
+ assert e.type == 'in'
+ assert e.unlock_time == 0
+ assert e.subaddr_index.major == 0
+ assert e.subaddr_indices == [{'major': 0, 'minor': 0}]
+ assert e.address == '46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK'
+ assert e.double_spend_seen == False
+ assert e.confirmations == 1
+ assert e.amount == 1200000000000
+ assert e.fee == fee
+
+ res = self.wallet[2].get_balance()
+ assert res.balance == running_balances[2]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 9
+
+ print('Sending to integrated address')
+ self.wallet[0].refresh()
+ res = self.wallet[0].get_balance()
+ i_pid = '1111111122222222'
+ res = self.wallet[0].make_integrated_address(standard_address = '44Kbx4sJ7JDRDV5aAhLJzQCjDz2ViLRduE3ijDZu3osWKBjMGkV1XPk4pfDUMqt1Aiezvephdqm6YD19GKFD9ZcXVUTp6BW', payment_id = i_pid)
+ i_address = res.integrated_address
+ res = self.wallet[0].transfer([{'address': i_address, 'amount': 200000000}])
+ assert len(res.tx_hash) == 32*2
+ i_txid = res.tx_hash
+ assert len(res.tx_key) == 32*2
+ assert res.amount == 200000000
+ i_amount = res.amount
+ assert res.fee > 0
+ fee = res.fee
+ assert len(res.tx_blob) == 0
+ assert len(res.tx_metadata) == 0
+ assert len(res.multisig_txset) == 0
+ assert len(res.unsigned_txset) == 0
+
+ running_balances[0] -= 200000000 + fee
+
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.getlastblockheader()
+ running_balances[0] += res.block_header.reward
+ running_balances[1] += 200000000
+
+ self.wallet[0].refresh()
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ self.wallet[1].refresh()
+ res = self.wallet[1].get_balance()
+ assert res.balance == running_balances[1]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 9
+
+ self.wallet[2].refresh()
+ res = self.wallet[2].get_balance()
+ assert res.balance == running_balances[2]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 8
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.getlastblockheader()
+ running_balances[0] += res.block_header.reward
+
+ self.wallet[0].refresh()
+ res = self.wallet[0].get_balance()
+ assert res.balance == running_balances[0]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 59
+
+ self.wallet[1].refresh()
+ res = self.wallet[1].get_balance()
+ assert res.balance == running_balances[1]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 8
+
+ self.wallet[2].refresh()
+ res = self.wallet[2].get_balance()
+ assert res.balance == running_balances[2]
+ assert res.unlocked_balance <= res.balance
+ assert res.blocks_to_unlock == 7
+
+
+ def check_get_bulk_payments(self):
+ print('Checking get_bulk_payments')
+
+ daemon = Daemon()
+ res = daemon.get_info()
+ height = res.height
+
+ self.wallet[0].refresh()
+ res = self.wallet[0].get_bulk_payments()
+ assert len(res.payments) >= 83 # at least 83 coinbases
+ res = self.wallet[0].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
+ assert 'payments' not in res or len(res.payments) == 0
+ res = self.wallet[0].get_bulk_payments(min_block_height = height)
+ assert 'payments' not in res or len(res.payments) == 0
+ res = self.wallet[0].get_bulk_payments(min_block_height = height - 40)
+ assert len(res.payments) >= 39 # coinbases
+
+ self.wallet[1].refresh()
+ res = self.wallet[1].get_bulk_payments()
+ assert len(res.payments) >= 3 # two txes to standard address were sent, plus one to integrated address
+ res = self.wallet[1].get_bulk_payments(payment_ids = ['1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde'])
+ assert len(res.payments) >= 2 # two txes were sent with that payment id
+ res = self.wallet[1].get_bulk_payments(payment_ids = ['ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff'])
+ assert 'payments' not in res or len(res.payments) == 0 # none with that payment id
+ res = self.wallet[1].get_bulk_payments(payment_ids = ['1111111122222222' + '0'*48])
+ assert len(res.payments) >= 1 # one tx to integrated address
+
+ self.wallet[2].refresh()
+ res = self.wallet[2].get_bulk_payments()
+ assert len(res.payments) >= 1 # one tx was sent
+ res = self.wallet[2].get_bulk_payments(payment_ids = ['1'*64, '1234500000012345abcde00000abcdeff1234500000012345abcde00000abcde', '2'*64])
+ assert len(res.payments) >= 1 # one tx was sent
+
+if __name__ == '__main__':
+ TransferTest().run_test()
diff --git a/tests/functional_tests/txpool.py b/tests/functional_tests/txpool.py
new file mode 100755
index 000000000..71109c9e5
--- /dev/null
+++ b/tests/functional_tests/txpool.py
@@ -0,0 +1,156 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test txpool
+"""
+
+from framework.daemon import Daemon
+from framework.wallet import Wallet
+
+class TransferTest():
+ def run_test(self):
+ self.create()
+ self.mine()
+ self.check_txpool()
+
+ def create(self):
+ print 'Creating wallet'
+ wallet = Wallet()
+ # close the wallet if any, will throw if none is loaded
+ try: wallet.close_wallet()
+ except: pass
+ seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
+ res = wallet.restore_deterministic_wallet(seed = seed)
+
+ def mine(self):
+ print("Mining some blocks")
+ daemon = Daemon()
+ wallet = Wallet()
+
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 80)
+ wallet.refresh()
+
+ def create_txes(self, address, ntxes):
+ print('Creating ' + str(ntxes) + ' transactions')
+
+ daemon = Daemon()
+ wallet = Wallet()
+
+ dst = {'address': address, 'amount': 1000000000000}
+
+ txes = {}
+ for i in range(ntxes):
+ res = wallet.transfer([dst], get_tx_hex = True)
+ txes[res.tx_hash] = res
+
+ return txes
+
+ def check_txpool(self):
+ daemon = Daemon()
+ wallet = Wallet()
+
+ res = daemon.get_info()
+ height = res.height
+ txpool_size = res.tx_pool_size
+
+ txes = self.create_txes('46r4nYSevkfBUMhuykdK3gQ98XDqDTYW1hNLaXNvjpsJaSbNtdXh1sKMsdVgqkaihChAzEy29zEDPMR3NHQvGoZCLGwTerK', 5)
+
+ res = daemon.get_info()
+ assert res.tx_pool_size == txpool_size + 5
+ txpool_size = res.tx_pool_size
+
+ res = daemon.get_transaction_pool()
+ assert len(res.transactions) == txpool_size
+ for txid in txes.keys():
+ x = [x for x in res.transactions if x.id_hash == txid]
+ assert len(x) == 1
+ x = x[0]
+ assert x.kept_by_block == False
+ assert x.last_failed_id_hash == '0'*64
+ assert x.double_spend_seen == False
+ assert x.weight >= x.blob_size
+
+ assert x.blob_size * 2 == len(txes[txid].tx_blob)
+ assert x.fee == txes[txid].fee
+ assert x.tx_blob == txes[txid].tx_blob
+
+ res = daemon.get_transaction_pool_hashes()
+ assert sorted(res.tx_hashes) == sorted(txes.keys())
+
+ print('Flushing 2 transactions')
+ daemon.flush_txpool([txes.keys()[1], txes.keys()[3]])
+ res = daemon.get_transaction_pool()
+ assert len(res.transactions) == txpool_size - 2
+ assert len([x for x in res.transactions if x.id_hash == txes.keys()[1]]) == 0
+ assert len([x for x in res.transactions if x.id_hash == txes.keys()[3]]) == 0
+
+ new_keys = txes.keys()
+ new_keys.remove(txes.keys()[1])
+ new_keys.remove(txes.keys()[3])
+ res = daemon.get_transaction_pool_hashes()
+ assert sorted(res.tx_hashes) == sorted(new_keys)
+
+ print('Flushing unknown transactions')
+ unknown_txids = ['1'*64, '2'*64, '3'*64]
+ daemon.flush_txpool(unknown_txids)
+ res = daemon.get_transaction_pool()
+ assert len(res.transactions) == txpool_size - 2
+
+ print('Mining transactions')
+ daemon.generateblocks('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', 1)
+ res = daemon.get_transaction_pool()
+ assert not 'transactions' in res or len(res.transactions) == txpool_size - 5
+ res = daemon.get_transaction_pool_hashes()
+ assert not 'tx_hashes' in res or len(res.tx_hashes) == 0
+
+ print('Popping block')
+ daemon.pop_blocks(1)
+ res = daemon.get_transaction_pool_hashes()
+ assert sorted(res.tx_hashes) == sorted(new_keys)
+ res = daemon.get_transaction_pool()
+ assert len(res.transactions) == txpool_size - 2
+ for txid in new_keys:
+ x = [x for x in res.transactions if x.id_hash == txid]
+ assert len(x) == 1
+ x = x[0]
+ assert x.kept_by_block == True
+ assert x.last_failed_id_hash == '0'*64
+ assert x.double_spend_seen == False
+ assert x.weight >= x.blob_size
+
+ assert x.blob_size * 2 == len(txes[txid].tx_blob)
+ assert x.fee == txes[txid].fee
+ assert x.tx_blob == txes[txid].tx_blob
+
+
+if __name__ == '__main__':
+ TransferTest().run_test()
diff --git a/tests/functional_tests/wallet_address.py b/tests/functional_tests/wallet_address.py
new file mode 100755
index 000000000..66a1633ca
--- /dev/null
+++ b/tests/functional_tests/wallet_address.py
@@ -0,0 +1,152 @@
+#!/usr/bin/env python3
+
+# Copyright (c) 2019 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+import time
+
+"""Test transaction creation RPC calls
+
+Test the following RPCs:
+ - [TODO: many tests still need to be written]
+
+"""
+
+from framework.wallet import Wallet
+
+class WalletAddressTest():
+ def run_test(self):
+ self.create()
+ self.check_main_address()
+ self.check_keys()
+ self.create_subaddresses()
+
+ def create(self):
+ print 'Creating wallet'
+ wallet = Wallet()
+ # close the wallet if any, will throw if none is loaded
+ try: wallet.close_wallet()
+ except: pass
+ seed = 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
+ res = wallet.restore_deterministic_wallet(seed = seed)
+ assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm'
+ assert res.seed == seed
+
+ def check_main_address(self):
+ print 'Getting address'
+ wallet = Wallet()
+ res = wallet.get_address()
+ assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == res.address
+ assert res.addresses[0].address_index == 0
+ assert res.addresses[0].used == False
+
+ def check_keys(self):
+ print 'Checking keys'
+ wallet = Wallet()
+ res = wallet.query_key('view_key')
+ assert res.key == '49774391fa5e8d249fc2c5b45dadef13534bf2483dede880dac88f061e809100'
+ res = wallet.query_key('spend_key')
+ assert res.key == '148d78d2aba7dbca5cd8f6abcfb0b3c009ffbdbea1ff373d50ed94d78286640e'
+ res = wallet.query_key('mnemonic')
+ assert res.key == 'velvet lymph giddy number token physics poetry unquoted nibs useful sabotage limits benches lifestyle eden nitrogen anvil fewest avoid batch vials washing fences goat unquoted'
+
+ def create_subaddresses(self):
+ print 'Creating subaddresses'
+ wallet = Wallet()
+ res = wallet.create_account("idx1")
+ assert res.account_index == 1, res
+ assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res
+ res = wallet.create_account("idx2")
+ assert res.account_index == 2, res
+ assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res
+
+ res = wallet.get_address(0, 0)
+ assert res.address == '42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm', res
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address_index == 0, res
+ res = wallet.get_address(1, 0)
+ assert res.address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf', res
+ assert len(res.addresses) == 1
+ assert res.addresses[0].label == 'idx1', res
+ assert res.addresses[0].address_index == 0, res
+ res = wallet.get_address(2, 0)
+ assert res.address == '8Bdb75y2MhvbkvaBnG7vYP6DCNneLWcXqNmfPmyyDkavAUUgrHQEAhTNK3jEq69kGPDrd3i5inPivCwTvvA12eQ4SJk9iyy', res
+ assert len(res.addresses) == 1
+ assert res.addresses[0].label == 'idx2', res
+ assert res.addresses[0].address_index == 0, res
+
+ res = wallet.create_address(0, "sub_0_1")
+ res = wallet.create_address(1, "sub_1_1")
+ res = wallet.create_address(1, "sub_1_2")
+
+ res = wallet.get_address(0, [1])
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == '84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF'
+ assert res.addresses[0].label == 'sub_0_1'
+ res = wallet.get_address(1, [1])
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4'
+ assert res.addresses[0].label == 'sub_1_1'
+ res = wallet.get_address(1, [2])
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
+ assert res.addresses[0].label == 'sub_1_2'
+ res = wallet.get_address(1, [0, 1, 2])
+ assert len(res.addresses) == 3
+ assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf'
+ assert res.addresses[0].label == 'idx1'
+ assert res.addresses[1].address == '87qyoPVaEcWikVBmG1TaP1KumZ3hB3Q5f4wZRjuppNdwYjWzs2RgbLYQgtpdu2YdoTT3EZhiUGaPJQt2FsykeFZbCtaGXU4'
+ assert res.addresses[1].label == 'sub_1_1'
+ assert res.addresses[2].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
+ assert res.addresses[2].label == 'sub_1_2'
+
+ res = wallet.label_address((1, 2), "sub_1_2_new")
+ res = wallet.get_address(1, [2])
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == '87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB'
+ assert res.addresses[0].label == 'sub_1_2_new'
+
+ res = wallet.label_account(1, "idx1_new")
+ res = wallet.get_address(1, [0])
+ assert len(res.addresses) == 1
+ assert res.addresses[0].address == '82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf'
+ assert res.addresses[0].label == 'idx1_new'
+
+ res = wallet.get_address_index('87KfgTZ8ER5D3Frefqnrqif11TjVsTPaTcp37kqqKMrdDRUhpJRczeR7KiBmSHF32UJLP3HHhKUDmEQyJrv2mV8yFDCq8eB')
+ assert res.index == {'major': 1, 'minor': 2}
+ res = wallet.get_address_index('42ey1afDFnn4886T7196doS9GPMzexD9gXpsZJDwVjeRVdFCSoHnv7KPbBeGpzJBzHRCAs9UxqeoyFQMYbqSWYTfJJQAWDm')
+ assert res.index == {'major': 0, 'minor': 0}
+ res = wallet.get_address_index('84QRUYawRNrU3NN1VpFRndSukeyEb3Xpv8qZjjsoJZnTYpDYceuUTpog13D7qPxpviS7J29bSgSkR11hFFoXWk2yNdsR9WF')
+ assert res.index == {'major': 0, 'minor': 1}
+ res = wallet.get_address_index('82pP87g1Vkd3LUMssBCumk3MfyEsFqLAaGDf6oxddu61EgSFzt8gCwUD4tr3kp9TUfdPs2CnpD7xLZzyC1Ei9UsW3oyCWDf')
+ assert res.index == {'major': 1, 'minor': 0}
+
+if __name__ == '__main__':
+ WalletAddressTest().run_test()
diff --git a/tests/hash-target.cpp b/tests/hash-target.cpp
index 70368ce24..12acc5a67 100644
--- a/tests/hash-target.cpp
+++ b/tests/hash-target.cpp
@@ -40,7 +40,7 @@ using cryptonote::check_hash;
int main(int argc, char *argv[]) {
crypto::hash h;
- for (uint64_t diff = 1;; diff += 1 + (diff >> 8)) {
+ for (cryptonote::difficulty_type diff = 1;; diff += 1 + (diff >> 8)) {
for (uint16_t b = 0; b < 256; b++) {
memset(&h, b, sizeof(crypto::hash));
if (check_hash(h, diff) != (b == 0 || diff <= 255 / b)) {
@@ -50,7 +50,7 @@ int main(int argc, char *argv[]) {
memset(&h, 0, sizeof(crypto::hash));
((char *) &h)[31] = b;
if (check_hash(h, diff) != (diff <= 255 / b)) {
- return 1;
+ return 2;
}
}
}
@@ -58,11 +58,11 @@ int main(int argc, char *argv[]) {
uint64_t val = 0;
for (int i = 31; i >= 0; i--) {
val = val * 256 + 255;
- ((char *) &h)[i] = static_cast<char>(val / diff);
- val %= diff;
+ ((char *) &h)[i] = static_cast<char>(static_cast<uint64_t>(val / diff));
+ val %= diff.convert_to<uint64_t>();
}
if (check_hash(h, diff) != true) {
- return 1;
+ return 3;
}
if (diff > 1) {
for (int i = 0;; i++) {
@@ -74,7 +74,7 @@ int main(int argc, char *argv[]) {
}
}
if (check_hash(h, diff) != false) {
- return 1;
+ return 4;
}
}
}
diff --git a/tests/performance_tests/CMakeLists.txt b/tests/performance_tests/CMakeLists.txt
index aa424da80..b36df10dc 100644
--- a/tests/performance_tests/CMakeLists.txt
+++ b/tests/performance_tests/CMakeLists.txt
@@ -31,6 +31,7 @@ set(performance_tests_sources
set(performance_tests_headers
check_tx_signature.h
+ check_hash.h
cn_slow_hash.h
construct_tx.h
derive_public_key.h
diff --git a/tests/performance_tests/check_hash.h b/tests/performance_tests/check_hash.h
new file mode 100644
index 000000000..d24001903
--- /dev/null
+++ b/tests/performance_tests/check_hash.h
@@ -0,0 +1,66 @@
+// Copyright (c) 2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#pragma once
+
+#include "string_tools.h"
+#include "cryptonote_basic/difficulty.h"
+
+template<uint64_t hash_target_high, uint64_t hash_target_low, uint64_t difficulty_high, uint64_t difficulty_low>
+class test_check_hash
+{
+public:
+ static const size_t loop_count = 100000;
+
+ bool init()
+ {
+ cryptonote::difficulty_type hash_target = hash_target_high;
+ hash_target = (hash_target << 64) | hash_target_low;
+ difficulty = difficulty_high;
+ difficulty = (difficulty << 64) | difficulty_low;
+ boost::multiprecision::uint256_t hash_value = std::numeric_limits<boost::multiprecision::uint256_t>::max() / hash_target;
+ ((uint64_t*)&hash)[0] = (hash_value << 64 >> 64).convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&hash)[1] = (hash_value << 64 >> 64).convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&hash)[2] = (hash_value << 64 >> 64).convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&hash)[3] = (hash_value << 64 >> 64).convert_to<uint64_t>();
+ return true;
+ }
+
+ bool test()
+ {
+ cryptonote::check_hash_128(hash, difficulty);
+ return true;
+ }
+
+private:
+ crypto::hash hash;
+ cryptonote::difficulty_type difficulty;
+};
diff --git a/tests/performance_tests/main.cpp b/tests/performance_tests/main.cpp
index e6558a364..c32e0df20 100644
--- a/tests/performance_tests/main.cpp
+++ b/tests/performance_tests/main.cpp
@@ -38,6 +38,7 @@
// tests
#include "construct_tx.h"
#include "check_tx_signature.h"
+#include "check_hash.h"
#include "cn_slow_hash.h"
#include "derive_public_key.h"
#include "derive_secret_key.h"
@@ -181,6 +182,14 @@ int main(int argc, char** argv)
TEST_PERFORMANCE4(filter, p, test_check_tx_signature_aggregated_bulletproofs, 2, 2, 56, 16);
TEST_PERFORMANCE4(filter, p, test_check_tx_signature_aggregated_bulletproofs, 10, 2, 56, 16);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 1, 0, 1);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 0xffffffffffffffff, 0, 0xffffffffffffffff);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 0, 0xffffffffffffffff, 0, 1);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 1, 0, 1, 0);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 1, 0, 0, 1);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 0xffffffffffffffff, 0xffffffffffffffff, 0, 1);
+ TEST_PERFORMANCE4(filter, p, test_check_hash, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff, 0xffffffffffffffff);
+
TEST_PERFORMANCE0(filter, p, test_is_out_to_acc);
TEST_PERFORMANCE0(filter, p, test_is_out_to_acc_precomp);
TEST_PERFORMANCE0(filter, p, test_generate_key_image_helper);
diff --git a/tests/unit_tests/CMakeLists.txt b/tests/unit_tests/CMakeLists.txt
index bfaea6add..56a1f8c4d 100644
--- a/tests/unit_tests/CMakeLists.txt
+++ b/tests/unit_tests/CMakeLists.txt
@@ -43,6 +43,7 @@ set(unit_tests_sources
crypto.cpp
decompose_amount_into_digits.cpp
device.cpp
+ difficulty.cpp
dns_resolver.cpp
epee_boosted_tcp_server.cpp
epee_levin_protocol_handler_async.cpp
diff --git a/tests/unit_tests/difficulty.cpp b/tests/unit_tests/difficulty.cpp
new file mode 100644
index 000000000..090fecc84
--- /dev/null
+++ b/tests/unit_tests/difficulty.cpp
@@ -0,0 +1,68 @@
+// Copyright (c) 2019, The Monero Project
+//
+// All rights reserved.
+//
+// Redistribution and use in source and binary forms, with or without modification, are
+// permitted provided that the following conditions are met:
+//
+// 1. Redistributions of source code must retain the above copyright notice, this list of
+// conditions and the following disclaimer.
+//
+// 2. Redistributions in binary form must reproduce the above copyright notice, this list
+// of conditions and the following disclaimer in the documentation and/or other
+// materials provided with the distribution.
+//
+// 3. Neither the name of the copyright holder nor the names of its contributors may be
+// used to endorse or promote products derived from this software without specific
+// prior written permission.
+//
+// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+// EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+// MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+// THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+#include "gtest/gtest.h"
+#include "cryptonote_basic/difficulty.h"
+
+static cryptonote::difficulty_type MKDIFF(uint64_t high, uint64_t low)
+{
+ cryptonote::difficulty_type d = high;
+ d = (d << 64) | low;
+ return d;
+}
+
+static crypto::hash MKHASH(uint64_t high, uint64_t low)
+{
+ cryptonote::difficulty_type hash_target = high;
+ hash_target = (hash_target << 64) | low;
+ boost::multiprecision::uint256_t hash_value = std::numeric_limits<boost::multiprecision::uint256_t>::max() / hash_target;
+ crypto::hash h;
+ ((uint64_t*)&h)[0] = hash_value.convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&h)[1] = hash_value.convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&h)[2] = hash_value.convert_to<uint64_t>();
+ hash_value >>= 64;
+ ((uint64_t*)&h)[3] = hash_value.convert_to<uint64_t>();
+ return h;
+}
+
+TEST(difficulty, check_hash)
+{
+ ASSERT_TRUE(cryptonote::check_hash(MKHASH(0, 1), MKDIFF(0, 1)));
+ ASSERT_FALSE(cryptonote::check_hash(MKHASH(0, 1), MKDIFF(0, 2)));
+
+ ASSERT_TRUE(cryptonote::check_hash(MKHASH(0, 0xffffffffffffffff), MKDIFF(0, 0xffffffffffffffff)));
+ ASSERT_FALSE(cryptonote::check_hash(MKHASH(0, 0xffffffffffffffff), MKDIFF(1, 0)));
+
+ ASSERT_TRUE(cryptonote::check_hash(MKHASH(1, 1), MKDIFF(1, 1)));
+ ASSERT_FALSE(cryptonote::check_hash(MKHASH(1, 1), MKDIFF(1, 2)));
+
+ ASSERT_TRUE(cryptonote::check_hash(MKHASH(0xffffffffffffffff, 1), MKDIFF(0xffffffffffffffff, 1)));
+ ASSERT_FALSE(cryptonote::check_hash(MKHASH(0xffffffffffffffff, 1), MKDIFF(0xffffffffffffffff, 2)));
+}
diff --git a/tests/unit_tests/serialization.cpp b/tests/unit_tests/serialization.cpp
index eb70caefc..27b14ffff 100644
--- a/tests/unit_tests/serialization.cpp
+++ b/tests/unit_tests/serialization.cpp
@@ -1187,3 +1187,26 @@ TEST(Serialization, portability_signed_tx)
ASSERT_TRUE(epee::string_tools::pod_to_hex(ki1) == "d54cbd435a8d636ad9b01b8d4f3eb13bd0cf1ce98eddf53ab1617f9b763e66c0");
ASSERT_TRUE(epee::string_tools::pod_to_hex(ki2) == "6c3cd6af97c4070a7aef9b1344e7463e29c7cd245076fdb65da447a34da3ca76");
}
+
+TEST(Serialization, difficulty_type)
+{
+ std::vector<cryptonote::difficulty_type> v_original;
+
+ for(int i = 0; i != 100; i++)
+ {
+ v_original.push_back(cryptonote::difficulty_type("117868131154734361989189100"));
+ if(v_original.size() > 1)
+ v_original.back() *= v_original[v_original.size()-2];
+ }
+
+ std::stringstream ss;
+ boost::archive::portable_binary_oarchive a(ss);
+ a << v_original;
+
+ std::vector<cryptonote::difficulty_type> v_unserialized;
+
+ boost::archive::portable_binary_iarchive a2(ss);
+ a2 >> v_unserialized;
+
+ ASSERT_EQ(v_original, v_unserialized);
+}
diff --git a/utils/python-rpc/console.py b/utils/python-rpc/console.py
new file mode 100755
index 000000000..ab0d9f27f
--- /dev/null
+++ b/utils/python-rpc/console.py
@@ -0,0 +1,49 @@
+#!/usr/bin/env python
+
+from __future__ import print_function
+import sys
+import subprocess
+import socket
+from framework import rpc
+from framework import wallet
+from framework import daemon
+
+USAGE = 'usage: python -i console.py <port>'
+try:
+ port = int(sys.argv[1])
+except:
+ print(USAGE)
+ sys.exit(1)
+
+# check for open port
+s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+s.settimeout(1)
+if s.connect_ex(('127.0.0.1', port)) != 0:
+ print('No wallet or daemon RPC on port ' + str(port))
+ sys.exit(1)
+s.close()
+
+# both wallet and daemon have a get_version JSON RPC
+rpc = rpc.JSONRPC('{protocol}://{host}:{port}'.format(protocol='http', host='127.0.0.1', port=port))
+get_version = {
+ 'method': 'get_version',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+}
+try:
+ res = rpc.send_json_rpc_request(get_version)
+except Exception, e:
+ print('Failed to call version RPC: ' + str(e))
+ sys.exit(1)
+
+if 'version' not in res:
+ print('Server is not a monero process')
+ sys.exit(1)
+
+if 'status' in res:
+ rpc = daemon.Daemon(port=port)
+else:
+ rpc = wallet.Wallet(port=port)
+
+print('Connected to %s RPC on port %u' % ('daemon' if 'status' in res else 'wallet', port))
+print('The \'rpc\' object may now be used to use the API')
diff --git a/tests/functional_tests/test_framework/__init__.py b/utils/python-rpc/framework/__init__.py
index e69de29bb..e69de29bb 100644
--- a/tests/functional_tests/test_framework/__init__.py
+++ b/utils/python-rpc/framework/__init__.py
diff --git a/utils/python-rpc/framework/daemon.py b/utils/python-rpc/framework/daemon.py
new file mode 100644
index 000000000..f60fe62db
--- /dev/null
+++ b/utils/python-rpc/framework/daemon.py
@@ -0,0 +1,219 @@
+# Copyright (c) 2018 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Daemon class to make rpc calls and store state."""
+
+from .rpc import JSONRPC
+
+class Daemon(object):
+
+ def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0):
+ self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18180+idx))
+
+ def getblocktemplate(self, address):
+ getblocktemplate = {
+ 'method': 'getblocktemplate',
+ 'params': {
+ 'wallet_address': address,
+ 'reserve_size' : 1
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getblocktemplate)
+
+ def send_raw_transaction(self, tx_as_hex, do_not_relay = False):
+ send_raw_transaction = {
+ 'tx_as_hex': tx_as_hex,
+ 'do_not_relay': do_not_relay,
+ }
+ return self.rpc.send_request("/send_raw_transaction", send_raw_transaction)
+
+ def submitblock(self, block):
+ submitblock = {
+ 'method': 'submitblock',
+ 'params': [ block ],
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(submitblock)
+
+ def getblock(self, height=0):
+ getblock = {
+ 'method': 'getblock',
+ 'params': {
+ 'height': height
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getblock)
+
+ def getlastblockheader(self):
+ getlastblockheader = {
+ 'method': 'getlastblockheader',
+ 'params': {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getlastblockheader)
+
+ def getblockheaderbyhash(self, hash):
+ getblockheaderbyhash = {
+ 'method': 'getblockheaderbyhash',
+ 'params': {
+ 'hash': hash,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getblockheaderbyhash)
+
+ def getblockheaderbyheight(self, height):
+ getblockheaderbyheight = {
+ 'method': 'getblockheaderbyheight',
+ 'params': {
+ 'height': height,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getblockheaderbyheight)
+
+ def getblockheadersrange(self, start_height, end_height, fill_pow_hash = False):
+ getblockheadersrange = {
+ 'method': 'getblockheadersrange',
+ 'params': {
+ 'start_height': start_height,
+ 'end_height': end_height,
+ 'fill_pow_hash': fill_pow_hash,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(getblockheadersrange)
+
+ def get_connections(self):
+ get_connections = {
+ 'method': 'get_connections',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_connections)
+
+ def get_info(self):
+ get_info = {
+ 'method': 'get_info',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_info)
+
+ def hard_fork_info(self):
+ hard_fork_info = {
+ 'method': 'hard_fork_info',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(hard_fork_info)
+
+ def generateblocks(self, address, blocks=1):
+ generateblocks = {
+ 'method': 'generateblocks',
+ 'params': {
+ 'amount_of_blocks' : blocks,
+ 'reserve_size' : 20,
+ 'wallet_address': address
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(generateblocks)
+
+ def get_height(self):
+ get_height = {
+ 'method': 'get_height',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_request("/get_height", get_height)
+
+ def pop_blocks(self, nblocks = 1):
+ pop_blocks = {
+ 'nblocks' : nblocks,
+ }
+ return self.rpc.send_request("/pop_blocks", pop_blocks)
+
+ def start_mining(self, miner_address, threads_count = 0, do_background_mining = False, ignore_battery = False):
+ start_mining = {
+ 'miner_address' : miner_address,
+ 'threads_count' : threads_count,
+ 'do_background_mining' : do_background_mining,
+ 'ignore_battery' : ignore_battery,
+ }
+ return self.rpc.send_request('/start_mining', start_mining)
+
+ def stop_mining(self):
+ stop_mining = {
+ }
+ return self.rpc.send_request('/stop_mining', stop_mining)
+
+ def mining_status(self):
+ mining_status = {
+ }
+ return self.rpc.send_request('/mining_status', mining_status)
+
+ def get_transaction_pool(self):
+ get_transaction_pool = {
+ }
+ return self.rpc.send_request('/get_transaction_pool', get_transaction_pool)
+
+ def get_transaction_pool_hashes(self):
+ get_transaction_pool_hashes = {
+ }
+ return self.rpc.send_request('/get_transaction_pool_hashes', get_transaction_pool_hashes)
+
+ def flush_txpool(self, txids = []):
+ flush_txpool = {
+ 'method': 'flush_txpool',
+ 'params': {
+ 'txids': txids
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(flush_txpool)
+
+ def get_version(self):
+ get_version = {
+ 'method': 'get_version',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_version)
diff --git a/tests/functional_tests/test_framework/rpc.py b/utils/python-rpc/framework/rpc.py
index b21df7b93..b857be4d2 100644
--- a/tests/functional_tests/test_framework/rpc.py
+++ b/utils/python-rpc/framework/rpc.py
@@ -29,21 +29,55 @@
import requests
import json
+class Response(dict):
+ def __init__(self, d):
+ for k in d.keys():
+ if type(d[k]) == dict:
+ self[k] = Response(d[k])
+ elif type(d[k]) == list:
+ self[k] = []
+ for i in range(len(d[k])):
+ if type(d[k][i]) == dict:
+ self[k].append(Response(d[k][i]))
+ else:
+ self[k].append(d[k][i])
+ else:
+ self[k] = d[k]
+
+ def __getattr__(self, key):
+ return self[key]
+ def __setattr__(self, key, value):
+ self[key] = value
+ def __eq__(self, other):
+ if type(other) == dict:
+ return self == Response(other)
+ if self.keys() != other.keys():
+ return False
+ for k in self.keys():
+ if self[k] != other[k]:
+ return False
+ return True
+
class JSONRPC(object):
def __init__(self, url):
self.url = url
- def send_request(self, inputs):
+ def send_request(self, path, inputs, result_field = None):
res = requests.post(
- self.url,
+ self.url + path,
data=json.dumps(inputs),
headers={'content-type': 'application/json'})
res = res.json()
assert 'error' not in res, res
- return res['result']
+ if result_field:
+ res = res[result_field]
+
+ return Response(res)
+ def send_json_rpc_request(self, inputs):
+ return self.send_request("/json_rpc", inputs, 'result')
diff --git a/utils/python-rpc/framework/wallet.py b/utils/python-rpc/framework/wallet.py
new file mode 100644
index 000000000..ea683b8c5
--- /dev/null
+++ b/utils/python-rpc/framework/wallet.py
@@ -0,0 +1,600 @@
+# Copyright (c) 2018 The Monero Project
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without modification, are
+# permitted provided that the following conditions are met:
+#
+# 1. Redistributions of source code must retain the above copyright notice, this list of
+# conditions and the following disclaimer.
+#
+# 2. Redistributions in binary form must reproduce the above copyright notice, this list
+# of conditions and the following disclaimer in the documentation and/or other
+# materials provided with the distribution.
+#
+# 3. Neither the name of the copyright holder nor the names of its contributors may be
+# used to endorse or promote products derived from this software without specific
+# prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
+# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
+# MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
+# THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
+# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
+# STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+"""Daemon class to make rpc calls and store state."""
+
+from .rpc import JSONRPC
+
+class Wallet(object):
+
+ def __init__(self, protocol='http', host='127.0.0.1', port=0, idx=0):
+ self.rpc = JSONRPC('{protocol}://{host}:{port}'.format(protocol=protocol, host=host, port=port if port else 18090+idx))
+
+ def make_uniform_destinations(self, address, transfer_amount, transfer_number_of_destinations=1):
+ destinations = []
+ for i in range(transfer_number_of_destinations):
+ destinations.append({"amount":transfer_amount,"address":address})
+ return destinations
+
+ def make_destinations(self, addresses, transfer_amounts):
+ destinations = []
+ for i in range(len(addresses)):
+ destinations.append({'amount':transfer_amounts[i],'address':addresses[i]})
+ return destinations
+
+ def transfer(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False):
+ transfer = {
+ 'method': 'transfer',
+ 'params': {
+ 'destinations': destinations,
+ 'account_index': account_index,
+ 'subaddr_indices': subaddr_indices,
+ 'priority': priority,
+ 'ring_size' : ring_size,
+ 'unlock_time' : unlock_time,
+ 'payment_id' : payment_id,
+ 'get_tx_key' : get_tx_key,
+ 'do_not_relay' : do_not_relay,
+ 'get_tx_hex' : get_tx_hex,
+ 'get_tx_metadata' : get_tx_metadata,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(transfer)
+
+ def transfer_split(self, destinations, account_index = 0, subaddr_indices = [], priority = 0, ring_size = 0, unlock_time = 0, payment_id = '', get_tx_key = True, do_not_relay = False, get_tx_hex = False, get_tx_metadata = False):
+ transfer = {
+ "method": "transfer_split",
+ "params": {
+ 'destinations': destinations,
+ 'account_index': account_index,
+ 'subaddr_indices': subaddr_indices,
+ 'priority': priority,
+ 'ring_size' : ring_size,
+ 'unlock_time' : unlock_time,
+ 'payment_id' : payment_id,
+ 'get_tx_key' : get_tx_key,
+ 'do_not_relay' : do_not_relay,
+ 'get_tx_hex' : get_tx_hex,
+ 'get_tx_metadata' : get_tx_metadata,
+ },
+ "jsonrpc": "2.0",
+ "id": "0"
+ }
+ return self.rpc.send_json_rpc_request(transfer)
+
+ def get_bulk_payments(self, payment_ids = [], min_block_height = 0):
+ get_bulk_payments = {
+ 'method': 'get_bulk_payments',
+ 'params': {
+ 'payment_ids': payment_ids,
+ 'min_block_height': min_block_height,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_bulk_payments)
+
+ def describe_transfer(self, unsigned_txset):
+ describe_transfer = {
+ 'method': 'describe_transfer',
+ 'params': {
+ 'unsigned_txset': unsigned_txset,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(describe_transfer)
+
+ def create_wallet(self, index=''):
+ create_wallet = {
+ 'method': 'create_wallet',
+ 'params': {
+ 'filename': 'testWallet' + index,
+ 'password' : '',
+ 'language' : 'English'
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(create_wallet)
+
+ def get_balance(self, account_index = 0, address_indices = [], all_accounts = False):
+ get_balance = {
+ 'method': 'get_balance',
+ 'params': {
+ 'account_index': account_index,
+ 'address_indices': address_indices,
+ 'all_accounts': all_accounts,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_balance)
+
+ def sweep_dust(self):
+ sweep_dust = {
+ 'method': 'sweep_dust',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(sweep_dust)
+
+ def sweep_all(self, address):
+ sweep_all = {
+ 'method': 'sweep_all',
+ 'params' : {
+ 'address' : ''
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(sweep_all)
+
+ def get_address(self, account_index = 0, subaddresses = []):
+ get_address = {
+ 'method': 'get_address',
+ 'params' : {
+ 'account_index' : account_index,
+ 'address_index': subaddresses
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_address)
+
+ def create_account(self, label = ""):
+ create_account = {
+ 'method': 'create_account',
+ 'params' : {
+ 'label': label
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(create_account)
+
+ def create_address(self, account_index = 0, label = ""):
+ create_address = {
+ 'method': 'create_address',
+ 'params' : {
+ 'account_index': account_index,
+ 'label': label
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(create_address)
+
+ def label_address(self, subaddress_index, label):
+ label_address = {
+ 'method': 'label_address',
+ 'params' : {
+ 'index': { 'major': subaddress_index[0], 'minor': subaddress_index[1]},
+ 'label': label
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(label_address)
+
+ def label_account(self, account_index, label):
+ label_account = {
+ 'method': 'label_account',
+ 'params' : {
+ 'account_index': account_index,
+ 'label': label
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(label_account)
+
+ def get_address_index(self, address):
+ get_address_index = {
+ 'method': 'get_address_index',
+ 'params' : {
+ 'address': address
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_address_index)
+
+ def query_key(self, key_type):
+ query_key = {
+ 'method': 'query_key',
+ 'params' : {
+ 'key_type': key_type
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(query_key)
+
+ def restore_deterministic_wallet(self, seed = '', seed_offset = '', filename = '', restore_height = 0, password = '', language = ''):
+ restore_deterministic_wallet = {
+ 'method': 'restore_deterministic_wallet',
+ 'params' : {
+ 'restore_height': restore_height,
+ 'filename': filename,
+ 'seed': seed,
+ 'seed_offset': seed_offset,
+ 'password': password,
+ 'language': language
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(restore_deterministic_wallet)
+
+ def generate_from_keys(self, restore_height = 0, filename = "", password = "", address = "", spendkey = "", viewkey = ""):
+ generate_from_keys = {
+ 'method': 'generate_from_keys',
+ 'params' : {
+ 'restore_height': restore_height,
+ 'filename': filename,
+ 'address': address,
+ 'spendkey': spendkey,
+ 'viewkey': viewkey,
+ 'password': password,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(generate_from_keys)
+
+ def close_wallet(self):
+ close_wallet = {
+ 'method': 'close_wallet',
+ 'params' : {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(close_wallet)
+
+ def refresh(self):
+ refresh = {
+ 'method': 'refresh',
+ 'params' : {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(refresh)
+
+ def incoming_transfers(self, transfer_type='all', account_index = 0, subaddr_indices = []):
+ incoming_transfers = {
+ 'method': 'incoming_transfers',
+ 'params' : {
+ 'transfer_type': transfer_type,
+ 'account_index': account_index,
+ 'subaddr_indices': subaddr_indices,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(incoming_transfers)
+
+ def get_transfers(self, in_ = True, out = True, pending = True, failed = True, pool = True, min_height = None, max_height = None, account_index = 0, subaddr_indices = [], all_accounts = False):
+ get_transfers = {
+ 'method': 'get_transfers',
+ 'params' : {
+ 'in': in_,
+ 'out': out,
+ 'pending': pending,
+ 'failed': failed,
+ 'pool': pool,
+ 'min_height': min_height,
+ 'max_height': max_height,
+ 'filter_by_height': min_height or max_height,
+ 'account_index': account_index,
+ 'subaddr_indices': subaddr_indices,
+ 'all_accounts': all_accounts,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_transfers)
+
+ def make_integrated_address(self, standard_address = '', payment_id = ''):
+ make_integrated_address = {
+ 'method': 'make_integrated_address',
+ 'params' : {
+ 'standard_address': standard_address,
+ 'payment_id': payment_id,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(make_integrated_address)
+
+ def split_integrated_address(self, integrated_address):
+ split_integrated_address = {
+ 'method': 'split_integrated_address',
+ 'params' : {
+ 'integrated_address': integrated_address,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(split_integrated_address)
+
+ def auto_refresh(self, enable, period = 0):
+ auto_refresh = {
+ 'method': 'auto_refresh',
+ 'params' : {
+ 'enable': enable,
+ 'period': period
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(auto_refresh)
+
+ def set_daemon(self, address, trusted = False, ssl_support = "autodetect", ssl_private_key_path = "", ssl_certificate_path = "", ssl_allowed_certificates = [], ssl_allowed_fingerprints = [], ssl_allow_any_cert = False):
+ set_daemon = {
+ 'method': 'set_daemon',
+ 'params' : {
+ 'address': address,
+ 'trusted': trusted,
+ 'ssl_support': ssl_support,
+ 'ssl_private_key_path': ssl_private_key_path,
+ 'ssl_certificate_path': ssl_certificate_path,
+ 'ssl_allowed_certificates': ssl_allowed_certificates,
+ 'ssl_allowed_fingerprints': ssl_allowed_fingerprints,
+ 'ssl_allow_any_cert': ssl_allow_any_cert,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(set_daemon)
+
+ def is_multisig(self):
+ is_multisig = {
+ 'method': 'is_multisig',
+ 'params' : {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(is_multisig)
+
+ def prepare_multisig(self):
+ prepare_multisig = {
+ 'method': 'prepare_multisig',
+ 'params' : {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(prepare_multisig)
+
+ def make_multisig(self, multisig_info, threshold, password = ''):
+ make_multisig = {
+ 'method': 'make_multisig',
+ 'params' : {
+ 'multisig_info': multisig_info,
+ 'threshold': threshold,
+ 'password': password,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(make_multisig)
+
+ def exchange_multisig_keys(self, multisig_info, password = ''):
+ exchange_multisig_keys = {
+ 'method': 'exchange_multisig_keys',
+ 'params' : {
+ 'multisig_info': multisig_info,
+ 'password': password,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(exchange_multisig_keys)
+
+ def export_multisig_info(self):
+ export_multisig_info = {
+ 'method': 'export_multisig_info',
+ 'params' : {
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(export_multisig_info)
+
+ def import_multisig_info(self, info = []):
+ import_multisig_info = {
+ 'method': 'import_multisig_info',
+ 'params' : {
+ 'info': info
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(import_multisig_info)
+
+ def sign_multisig(self, tx_data_hex):
+ sign_multisig = {
+ 'method': 'sign_multisig',
+ 'params' : {
+ 'tx_data_hex': tx_data_hex
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(sign_multisig)
+
+ def submit_multisig(self, tx_data_hex):
+ submit_multisig = {
+ 'method': 'submit_multisig',
+ 'params' : {
+ 'tx_data_hex': tx_data_hex
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(submit_multisig)
+
+ def sign_transfer(self, unsigned_txset, export_raw = False, get_tx_keys = False):
+ sign_transfer = {
+ 'method': 'sign_transfer',
+ 'params' : {
+ 'unsigned_txset': unsigned_txset,
+ 'export_raw': export_raw,
+ 'get_tx_keys': get_tx_keys,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(sign_transfer)
+
+ def submit_transfer(self, tx_data_hex):
+ submit_transfer = {
+ 'method': 'submit_transfer',
+ 'params' : {
+ 'tx_data_hex': tx_data_hex,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(submit_transfer)
+
+ def get_tx_key(self, txid):
+ get_tx_key = {
+ 'method': 'get_tx_key',
+ 'params' : {
+ 'txid': txid,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_tx_key)
+
+ def check_tx_key(self, txid = '', tx_key = '', address = ''):
+ check_tx_key = {
+ 'method': 'check_tx_key',
+ 'params' : {
+ 'txid': txid,
+ 'tx_key': tx_key,
+ 'address': address,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(check_tx_key)
+
+ def get_tx_proof(self, txid = '', address = '', message = ''):
+ get_tx_proof = {
+ 'method': 'get_tx_proof',
+ 'params' : {
+ 'txid': txid,
+ 'address': address,
+ 'message': message,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_tx_proof)
+
+ def check_tx_proof(self, txid = '', address = '', message = '', signature = ''):
+ check_tx_proof = {
+ 'method': 'check_tx_proof',
+ 'params' : {
+ 'txid': txid,
+ 'address': address,
+ 'message': message,
+ 'signature': signature,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(check_tx_proof)
+
+ def get_reserve_proof(self, all_ = True, account_index = 0, amount = 0, message = ''):
+ get_reserve_proof = {
+ 'method': 'get_reserve_proof',
+ 'params' : {
+ 'all': all_,
+ 'account_index': account_index,
+ 'amount': amount,
+ 'message': message,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_reserve_proof)
+
+ def check_reserve_proof(self, address = '', message = '', signature = ''):
+ check_reserve_proof = {
+ 'method': 'check_reserve_proof',
+ 'params' : {
+ 'address': address,
+ 'message': message,
+ 'signature': signature,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(check_reserve_proof)
+
+ def sign(self, data):
+ sign = {
+ 'method': 'sign',
+ 'params' : {
+ 'data': data,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(sign)
+
+ def verify(self, data, address, signature):
+ verify = {
+ 'method': 'verify',
+ 'params' : {
+ 'data': data,
+ 'address': address,
+ 'signature': signature,
+ },
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(verify)
+
+ def get_version(self):
+ get_version = {
+ 'method': 'get_version',
+ 'jsonrpc': '2.0',
+ 'id': '0'
+ }
+ return self.rpc.send_json_rpc_request(get_version)