aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/crypto/CMakeLists.txt2
-rw-r--r--src/cryptonote_core/blockchain.cpp44
-rw-r--r--src/cryptonote_core/blockchain.h2
-rw-r--r--src/cryptonote_core/cryptonote_tx_utils.h7
-rw-r--r--src/device/CMakeLists.txt2
-rw-r--r--src/device/device.hpp21
-rw-r--r--src/device/device_default.cpp10
-rw-r--r--src/device/device_default.hpp6
-rw-r--r--src/device/device_ledger.cpp264
-rw-r--r--src/device/device_ledger.hpp20
-rw-r--r--src/device/log.hpp13
-rw-r--r--src/p2p/net_node.inl2
-rw-r--r--src/ringct/rctSigs.cpp8
-rw-r--r--src/simplewallet/simplewallet.cpp92
-rw-r--r--src/simplewallet/simplewallet.h6
-rw-r--r--src/wallet/wallet2.cpp147
-rw-r--r--src/wallet/wallet2.h26
-rw-r--r--tests/block_weight/block_weight.cpp5
-rwxr-xr-xtests/block_weight/block_weight.py3
-rwxr-xr-xtests/block_weight/compare.py9
-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
-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
41 files changed, 3564 insertions, 412 deletions
diff --git a/src/crypto/CMakeLists.txt b/src/crypto/CMakeLists.txt
index d22d59b36..3a4d09fd8 100644
--- a/src/crypto/CMakeLists.txt
+++ b/src/crypto/CMakeLists.txt
@@ -49,7 +49,7 @@ set(crypto_sources
CryptonightR_JIT.c
tree-hash.c)
-if(ARCH_ID STREQUAL "i386" OR ARCH_ID STREQUAL "x86_64" OR ARCH_ID STREQUAL "x86-64")
+if(ARCH_ID STREQUAL "i386" OR ARCH_ID STREQUAL "x86_64" OR ARCH_ID STREQUAL "x86-64" OR ARCH_ID STREQUAL "amd64")
list(APPEND crypto_sources CryptonightR_template.S)
endif()
diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp
index 263227148..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)
@@ -1281,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
diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h
index 89d8e7572..2cd4dc31b 100644
--- a/src/cryptonote_core/blockchain.h
+++ b/src/cryptonote_core/blockchain.h
@@ -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_tx_utils.h b/src/cryptonote_core/cryptonote_tx_utils.h
index cb1561c2d..d38aa7474 100644
--- a/src/cryptonote_core/cryptonote_tx_utils.h
+++ b/src/cryptonote_core/cryptonote_tx_utils.h
@@ -104,6 +104,13 @@ namespace cryptonote
std::vector<rct::key> &amount_keys,
crypto::public_key &out_eph_public_key) ;
+ bool generate_output_ephemeral_keys(const size_t tx_version, const cryptonote::account_keys &sender_account_keys, const crypto::public_key &txkey_pub, const crypto::secret_key &tx_key,
+ const cryptonote::tx_destination_entry &dst_entr, const boost::optional<cryptonote::account_public_address> &change_addr, const size_t output_index,
+ const bool &need_additional_txkeys, const std::vector<crypto::secret_key> &additional_tx_keys,
+ std::vector<crypto::public_key> &additional_tx_public_keys,
+ std::vector<rct::key> &amount_keys,
+ crypto::public_key &out_eph_public_key) ;
+
bool generate_genesis_block(
block& bl
, std::string const & genesis_tx
diff --git a/src/device/CMakeLists.txt b/src/device/CMakeLists.txt
index ffa1458b0..0b0686f61 100644
--- a/src/device/CMakeLists.txt
+++ b/src/device/CMakeLists.txt
@@ -75,4 +75,6 @@ target_link_libraries(device
${OPENSSL_CRYPTO_LIBRARIES}
${Boost_SERIALIZATION_LIBRARY}
PRIVATE
+ version
+ ${Blocks}
${EXTRA_LIBRARIES})
diff --git a/src/device/device.hpp b/src/device/device.hpp
index 65b38361b..2e485b1d6 100644
--- a/src/device/device.hpp
+++ b/src/device/device.hpp
@@ -27,21 +27,6 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
-
-/* Note about debug:
- * To debug Device you can def the following :
- * #define DEBUG_HWDEVICE
- * Activate debug mechanism:
- * - Add more trace
- * - All computation done by device are checked by default device.
- * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working
- * #define IODUMMYCRYPT_HWDEVICE 1
- * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value
- * #define IONOCRYPT_HWDEVICE 1
- * - It assumes sensitive data encryption is off on device side.
- */
-
-
#pragma once
#include "crypto/crypto.h"
@@ -211,6 +196,10 @@ namespace hw {
/* TRANSACTION */
/* ======================================================================= */
+ virtual void generate_tx_proof(const crypto::hash &prefix_hash,
+ const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
+ crypto::signature &sig) = 0;
+
virtual bool open_tx(crypto::secret_key &tx_key) = 0;
virtual bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) = 0;
@@ -220,6 +209,8 @@ namespace hw {
return encrypt_payment_id(payment_id, public_key, secret_key);
}
+ virtual rct::key genCommitmentMask(const rct::key &amount_key) = 0;
+
virtual bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) = 0;
virtual bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) = 0;
diff --git a/src/device/device_default.cpp b/src/device/device_default.cpp
index 999fbc22f..dc06ce237 100644
--- a/src/device/device_default.cpp
+++ b/src/device/device_default.cpp
@@ -37,7 +37,6 @@
#include "cryptonote_core/cryptonote_tx_utils.h"
#include "ringct/rctOps.h"
-#include "log.hpp"
#define ENCRYPTED_PAYMENT_ID_TAIL 0x8d
#define CHACHA8_KEY_TAIL 0x8c
@@ -273,6 +272,11 @@ namespace hw {
/* ======================================================================= */
/* TRANSACTION */
/* ======================================================================= */
+ void device_default::generate_tx_proof(const crypto::hash &prefix_hash,
+ const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
+ crypto::signature &sig) {
+ crypto::generate_tx_proof(prefix_hash, R, A, B, D, r, sig);
+ }
bool device_default::open_tx(crypto::secret_key &tx_key) {
cryptonote::keypair txkey = cryptonote::keypair::generate(*this);
@@ -349,6 +353,10 @@ namespace hw {
return true;
}
+ rct::key device_default::genCommitmentMask(const rct::key &amount_key) {
+ return rct::genCommitmentMask(amount_key);
+ }
+
bool device_default::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) {
rct::ecdhEncode(unmasked, sharedSec, short_amount);
return true;
diff --git a/src/device/device_default.hpp b/src/device/device_default.hpp
index 90d39495b..5252d4129 100644
--- a/src/device/device_default.hpp
+++ b/src/device/device_default.hpp
@@ -107,10 +107,16 @@ namespace hw {
/* TRANSACTION */
/* ======================================================================= */
+ void generate_tx_proof(const crypto::hash &prefix_hash,
+ const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
+ crypto::signature &sig) override;
+
bool open_tx(crypto::secret_key &tx_key) override;
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
+ rct::key genCommitmentMask(const rct::key &amount_key) override;
+
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_amount) override;
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_amount) override;
diff --git a/src/device/device_ledger.cpp b/src/device/device_ledger.cpp
index 0f197272c..d6033e189 100644
--- a/src/device/device_ledger.cpp
+++ b/src/device/device_ledger.cpp
@@ -27,8 +27,8 @@
// THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
//
+#include "version.h"
#include "device_ledger.hpp"
-#include "log.hpp"
#include "ringct/rctOps.h"
#include "cryptonote_basic/account.h"
#include "cryptonote_basic/subaddress_index.h"
@@ -173,6 +173,7 @@ namespace hw {
#define INS_SET_SIGNATURE_MODE 0x72
#define INS_GET_ADDITIONAL_KEY 0x74
#define INS_STEALTH 0x76
+ #define INS_GEN_COMMITMENT_MASK 0x77
#define INS_BLIND 0x78
#define INS_UNBLIND 0x7A
#define INS_GEN_TXOUT_KEYS 0x7B
@@ -180,6 +181,7 @@ namespace hw {
#define INS_MLSAG 0x7E
#define INS_CLOSE_TX 0x80
+ #define INS_GET_TX_PROOF 0xA0
#define INS_GET_RESPONSE 0xc0
@@ -302,8 +304,24 @@ namespace hw {
}
bool device_ledger::reset() {
- send_simple(INS_RESET);
- return true;
+ reset_buffer();
+ int offset = set_command_header_noopt(INS_RESET);
+ memmove(this->buffer_send+offset, MONERO_VERSION, strlen(MONERO_VERSION));
+ offset += strlen(MONERO_VERSION);
+ this->buffer_send[4] = offset-5;
+ this->length_send = offset;
+ this->exchange();
+
+ ASSERT_X(this->length_recv>=3, "Communication error, less than three bytes received. Check your application version.");
+
+ unsigned int device_version = 0;
+ device_version = VERSION(this->buffer_recv[0], this->buffer_recv[1], this->buffer_recv[2]);
+
+ ASSERT_X (device_version >= MINIMAL_APP_VERSION,
+ "Unsupported device application version: " << VERSION_MAJOR(device_version)<<"."<<VERSION_MINOR(device_version)<<"."<<VERSION_MICRO(device_version) <<
+ " At least " << MINIMAL_APP_VERSION_MAJOR<<"."<<MINIMAL_APP_VERSION_MINOR<<"."<<MINIMAL_APP_VERSION_MICRO<<" is required.");
+
+ return true;
}
unsigned int device_ledger::exchange(unsigned int ok, unsigned int mask) {
@@ -314,9 +332,9 @@ namespace hw {
this->length_recv -= 2;
this->sw = (this->buffer_recv[length_recv]<<8) | this->buffer_recv[length_recv+1];
+ logRESP();
ASSERT_SW(this->sw,ok,msk);
- logRESP();
return this->sw;
}
@@ -790,7 +808,11 @@ namespace hw {
const crypto::secret_key a_x = hw::ledger::decrypt(a);
const crypto::secret_key b_x = hw::ledger::decrypt(b);
crypto::secret_key r_x;
+ rct::key aG_x;
+ log_hexbuffer("sc_secret_add: [[IN]] a ", (char*)a_x.data, 32);
+ log_hexbuffer("sc_secret_add: [[IN]] b ", (char*)b_x.data, 32);
this->controle_device->sc_secret_add(r_x, a_x, b_x);
+ log_hexbuffer("sc_secret_add: [[OUT]] aG", (char*)r_x.data, 32);
#endif
int offset = set_command_header_noopt(INS_SECRET_KEY_ADD);
@@ -825,6 +847,11 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
crypto::public_key pub_x;
crypto::secret_key sec_x;
+ crypto::secret_key recovery_key_x;
+ if (recover) {
+ recovery_key_x = hw::ledger::decrypt(recovery_key);
+ log_hexbuffer("generate_keys: [[IN]] pub", (char*)recovery_key_x.data, 32);
+ }
#endif
send_simple(INS_GENERATE_KEYPAIR);
@@ -836,6 +863,9 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
crypto::secret_key sec_clear = hw::ledger::decrypt(sec);
sec_x = sec_clear;
+ log_hexbuffer("generate_keys: [[OUT]] pub", (char*)pub.data, 32);
+ log_hexbuffer("generate_keys: [[OUT]] sec", (char*)sec_clear.data, 32);
+
crypto::secret_key_to_public_key(sec_x,pub_x);
hw::ledger::check32("generate_keys", "pub", pub_x.data, pub.data);
#endif
@@ -850,7 +880,7 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
const crypto::public_key pub_x = pub;
- const crypto::secret_key sec_x = hw::ledger::decrypt(sec);
+ const crypto::secret_key sec_x = (sec == rct::rct2sk(rct::I)) ? sec: hw::ledger::decrypt(sec);
crypto::key_derivation derivation_x;
log_hexbuffer("generate_key_derivation: [[IN]] pub ", pub_x.data, 32);
log_hexbuffer("generate_key_derivation: [[IN]] sec ", sec_x.data, 32);
@@ -866,7 +896,6 @@ namespace hw {
assert(is_fake_view_key(sec));
r = crypto::generate_key_derivation(pub, this->viewkey, derivation);
} else {
-
int offset = set_command_header_noopt(INS_GEN_KEY_DERIVATION);
//pub
memmove(this->buffer_send+offset, pub.data, 32);
@@ -885,11 +914,11 @@ namespace hw {
}
#ifdef DEBUG_HWDEVICE
crypto::key_derivation derivation_clear ;
- if ((this->mode == TRANSACTION_PARSE) && has_view_key) {
- derivation_clear = derivation;
- }else {
- derivation_clear = hw::ledger::decrypt(derivation);
- }
+ if ((this->mode == TRANSACTION_PARSE) && has_view_key) {
+ derivation_clear = derivation;
+ } else {
+ derivation_clear = hw::ledger::decrypt(derivation);
+ }
hw::ledger::check32("generate_key_derivation", "derivation", derivation_x.data, derivation_clear.data);
#endif
@@ -1050,7 +1079,7 @@ namespace hw {
bool rc = this->controle_device->secret_key_to_public_key(sec_x, pub_x);
log_hexbuffer("secret_key_to_public_key: [[OUT]] pub", pub_x.data, 32);
if (!rc){
- log_message("secret_key_to_public_key", "secret_key rejected");
+ log_message("FAIL secret_key_to_public_key", "secret_key rejected");
}
#endif
@@ -1112,6 +1141,75 @@ namespace hw {
/* TRANSACTION */
/* ======================================================================= */
+ void device_ledger::generate_tx_proof(const crypto::hash &prefix_hash,
+ const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
+ crypto::signature &sig) {
+
+ AUTO_LOCK_CMD();
+
+ #ifdef DEBUG_HWDEVICE
+ const crypto::hash prefix_hash_x = prefix_hash;
+ const crypto::public_key R_x = R;
+ const crypto::public_key A_x = A;
+ const boost::optional<crypto::public_key> B_x = B;
+ const crypto::public_key D_x = D;
+ const crypto::secret_key r_x = hw::ledger::decrypt(r);
+ crypto::signature sig_x;
+ log_hexbuffer("generate_tx_proof: [[IN]] prefix_hash ", prefix_hash_x.data, 32);
+ log_hexbuffer("generate_tx_proof: [[IN]] R ", R_x.data, 32);
+ log_hexbuffer("generate_tx_proof: [[IN]] A ", A_x.data, 32);
+ if (B_x) {
+ log_hexbuffer("generate_tx_proof: [[IN]] B ", (*B_x).data, 32);
+ }
+ log_hexbuffer("generate_tx_proof: [[IN]] D ", D_x.data, 32);
+ log_hexbuffer("generate_tx_proof: [[IN]] r ", r_x.data, 32);
+ #endif
+
+
+ int offset = set_command_header(INS_GET_TX_PROOF);
+ //options
+ this->buffer_send[offset] = B?0x01:0x00;
+ offset += 1;
+ //prefix_hash
+ memmove(&this->buffer_send[offset], prefix_hash.data, 32);
+ offset += 32;
+ // R
+ memmove(&this->buffer_send[offset], R.data, 32);
+ offset += 32;
+ // A
+ memmove(&this->buffer_send[offset], A.data, 32);
+ offset += 32;
+ // B
+ if (B) {
+ memmove(&this->buffer_send[offset], (*B).data, 32);
+ } else {
+ memset(&this->buffer_send[offset], 0, 32);
+ }
+ offset += 32;
+ // D
+ memmove(&this->buffer_send[offset], D.data, 32);
+ offset += 32;
+ // r
+ memmove(&this->buffer_send[offset], r.data, 32);
+ offset += 32;
+
+ this->buffer_send[4] = offset-5;
+ this->length_send = offset;
+ this->exchange();
+
+ memmove(sig.c.data, &this->buffer_recv[0], 32);
+ memmove(sig.r.data, &this->buffer_recv[32], 32);
+ #ifdef DEBUG_HWDEVICE
+ log_hexbuffer("GENERATE_TX_PROOF: **c** ", sig.c.data, sizeof( sig.c.data));
+ log_hexbuffer("GENERATE_TX_PROOF: **r** ", sig.r.data, sizeof( sig.r.data));
+
+ this->controle_device->generate_tx_proof(prefix_hash_x, R_x, A_x, B_x, D_x, r_x, sig_x);
+ hw::ledger::check32("generate_tx_proof", "c", sig_x.c.data, sig.c.data);
+ hw::ledger::check32("generate_tx_proof", "r", sig_x.r.data, sig.r.data);
+
+ #endif
+ }
+
bool device_ledger::open_tx(crypto::secret_key &tx_key) {
AUTO_LOCK_CMD();
@@ -1130,7 +1228,11 @@ namespace hw {
this->exchange();
memmove(tx_key.data, &this->buffer_recv[32], 32);
-
+ #ifdef DEBUG_HWDEVICE
+ const crypto::secret_key r_x = hw::ledger::decrypt(tx_key);
+ log_hexbuffer("open_tx: [[OUT]] R ", (char*)&this->buffer_recv[0], 32);
+ log_hexbuffer("open_tx: [[OUT]] r ", r_x.data, 32);
+ #endif
return true;
}
@@ -1141,7 +1243,11 @@ namespace hw {
const crypto::public_key public_key_x = public_key;
const crypto::secret_key secret_key_x = hw::ledger::decrypt(secret_key);
crypto::hash8 payment_id_x = payment_id;
+ log_hexbuffer("encrypt_payment_id: [[IN]] payment_id ", payment_id_x.data, 32);
+ log_hexbuffer("encrypt_payment_id: [[IN]] public_key ", public_key_x.data, 32);
+ log_hexbuffer("encrypt_payment_id: [[IN]] secret_key ", secret_key_x.data, 32);
this->controle_device->encrypt_payment_id(payment_id_x, public_key_x, secret_key_x);
+ log_hexbuffer("encrypt_payment_id: [[OUT]] payment_id ", payment_id_x.data, 32);
#endif
int offset = set_command_header_noopt(INS_STEALTH);
@@ -1178,49 +1284,58 @@ namespace hw {
#ifdef DEBUG_HWDEVICE
const size_t &tx_version_x = tx_version;
- const cryptonote::account_keys sender_account_keys_x = sender_account_keys;
+ const cryptonote::account_keys sender_account_keys_x = hw::ledger::decrypt(sender_account_keys);
memmove((void*)sender_account_keys_x.m_view_secret_key.data, dbg_viewkey.data, 32);
- const crypto::public_key &txkey_pub_x = txkey_pub;
- const crypto::secret_key &tx_key_x = tx_key;
- const cryptonote::tx_destination_entry &dst_entr_x = dst_entr;
- const boost::optional<cryptonote::account_public_address> &change_addr_x = change_addr;
- const size_t &output_index_x = output_index;
- const bool &need_additional_txkeys_x = need_additional_txkeys;
- const std::vector<crypto::secret_key> &additional_tx_keys_x = additional_tx_keys;
+ const crypto::public_key txkey_pub_x = txkey_pub;
+ const crypto::secret_key tx_key_x = hw::ledger::decrypt(tx_key);
+ const cryptonote::tx_destination_entry dst_entr_x = dst_entr;
+ const boost::optional<cryptonote::account_public_address> change_addr_x = change_addr;
+ const size_t output_index_x = output_index;
+ const bool need_additional_txkeys_x = need_additional_txkeys;
+
+ std::vector<crypto::secret_key> additional_tx_keys_x;
+ for (const auto k: additional_tx_keys) {
+ additional_tx_keys_x.push_back(hw::ledger::decrypt(k));
+ }
+
std::vector<crypto::public_key> additional_tx_public_keys_x;
std::vector<rct::key> amount_keys_x;
crypto::public_key out_eph_public_key_x;
+
+ log_message ("generate_output_ephemeral_keys: [[IN]] tx_version", std::to_string(tx_version_x));
+ //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.view", sender_account_keys.m_sview_secret_key.data, 32);
+ //log_hexbuffer("generate_output_ephemeral_keys: [[IN]] sender_account_keys.spend", sender_account_keys.m_spend_secret_key.data, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] txkey_pub", txkey_pub_x.data, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] tx_key", tx_key_x.data, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.view", dst_entr_x.addr.m_view_public_key.data, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] dst_entr.spend", dst_entr_x.addr.m_spend_public_key.data, 32);
+ if (change_addr) {
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.view", (*change_addr_x).m_view_public_key.data, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] change_addr.spend", (*change_addr_x).m_spend_public_key.data, 32);
+ }
+ log_message ("generate_output_ephemeral_keys: [[IN]] output_index", std::to_string(output_index_x));
+ log_message ("generate_output_ephemeral_keys: [[IN]] need_additional_txkeys", std::to_string(need_additional_txkeys_x));
+ if(need_additional_txkeys_x) {
+ log_hexbuffer("generate_output_ephemeral_keys: [[IN]] additional_tx_keys[oi]", additional_tx_keys_x[output_index].data, 32);
+ }
this->controle_device->generate_output_ephemeral_keys(tx_version_x, sender_account_keys_x, txkey_pub_x, tx_key_x, dst_entr_x, change_addr_x, output_index_x, need_additional_txkeys_x, additional_tx_keys_x,
additional_tx_public_keys_x, amount_keys_x, out_eph_public_key_x);
+ if(need_additional_txkeys_x) {
+ log_hexbuffer("additional_tx_public_keys_x: [[OUT]] additional_tx_public_keys_x", additional_tx_public_keys_x.back().data, 32);
+ }
+ log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] amount_keys ", (char*)amount_keys_x.back().bytes, 32);
+ log_hexbuffer("generate_output_ephemeral_keys: [[OUT]] out_eph_public_key ", out_eph_public_key_x.data, 32);
#endif
+ ASSERT_X(tx_version > 1, "TX version not supported"<<tx_version);
+
// make additional tx pubkey if necessary
cryptonote::keypair additional_txkey;
if (need_additional_txkeys) {
additional_txkey.sec = additional_tx_keys[output_index];
}
- //compute derivation, out_eph_public_key, and amount key in one shot on device, to ensure checkable link
- const crypto::secret_key *sec;
- bool is_change;
-
- if (change_addr && dst_entr.addr == *change_addr)
- {
- // sending change to yourself; derivation = a*R
- is_change = true;
- sec = &sender_account_keys.m_view_secret_key;
- }
- else
- {
- is_change = false;
- if (dst_entr.is_subaddress && need_additional_txkeys) {
- sec = &additional_txkey.sec;
- } else {
- sec = &tx_key;
- }
- }
-
int offset = set_command_header_noopt(INS_GEN_TXOUT_KEYS);
//tx_version
this->buffer_send[offset+0] = tx_version>>24;
@@ -1228,8 +1343,11 @@ namespace hw {
this->buffer_send[offset+2] = tx_version>>8;
this->buffer_send[offset+3] = tx_version>>0;
offset += 4;
- //tx_sec
- memmove(&this->buffer_send[offset], sec->data, 32);
+ //tx_key
+ memmove(&this->buffer_send[offset], tx_key.data, 32);
+ offset += 32;
+ //txkey_pub
+ memmove(&this->buffer_send[offset], txkey_pub.data, 32);
offset += 32;
//Aout
memmove(&this->buffer_send[offset], dst_entr.addr.m_view_public_key.data, 32);
@@ -1244,6 +1362,7 @@ namespace hw {
this->buffer_send[offset+3] = output_index>>0;
offset += 4;
//is_change,
+ bool is_change = (change_addr && dst_entr.addr == *change_addr);
this->buffer_send[offset] = is_change;
offset++;
//is_subaddress
@@ -1252,6 +1371,13 @@ namespace hw {
//need_additional_key
this->buffer_send[offset] = need_additional_txkeys;
offset++;
+ //additional_tx_key
+ if (need_additional_txkeys) {
+ memmove(&this->buffer_send[offset], additional_txkey.sec.data, 32);
+ } else {
+ memset(&this->buffer_send[offset], 0, 32);
+ }
+ offset += 32;
this->buffer_send[4] = offset-5;
this->length_send = offset;
@@ -1259,15 +1385,8 @@ namespace hw {
offset = 0;
unsigned int recv_len = this->length_recv;
- if (need_additional_txkeys)
- {
- ASSERT_X(recv_len>=32, "Not enought data from device");
- memmove(additional_txkey.pub.data, &this->buffer_recv[offset], 32);
- additional_tx_public_keys.push_back(additional_txkey.pub);
- offset += 32;
- recv_len -= 32;
- }
- if (tx_version > 1)
+
+ //if (tx_version > 1)
{
ASSERT_X(recv_len>=32, "Not enought data from device");
crypto::secret_key scalar1;
@@ -1279,6 +1398,16 @@ namespace hw {
ASSERT_X(recv_len>=32, "Not enought data from device");
memmove(out_eph_public_key.data, &this->buffer_recv[offset], 32);
recv_len -= 32;
+ offset += 32;
+
+ if (need_additional_txkeys)
+ {
+ ASSERT_X(recv_len>=32, "Not enought data from device");
+ memmove(additional_txkey.pub.data, &this->buffer_recv[offset], 32);
+ additional_tx_public_keys.push_back(additional_txkey.pub);
+ offset += 32;
+ recv_len -= 32;
+ }
// add ABPkeys
this->add_output_key_mapping(dst_entr.addr.m_view_public_key, dst_entr.addr.m_spend_public_key, dst_entr.is_subaddress, is_change,
@@ -1286,9 +1415,10 @@ namespace hw {
amount_keys.back(), out_eph_public_key);
#ifdef DEBUG_HWDEVICE
+ log_hexbuffer("generate_output_ephemeral_keys: clear amount_key", (const char*)hw::ledger::decrypt(amount_keys.back()).bytes, 32);
hw::ledger::check32("generate_output_ephemeral_keys", "amount_key", (const char*)amount_keys_x.back().bytes, (const char*)hw::ledger::decrypt(amount_keys.back()).bytes);
if (need_additional_txkeys) {
- hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_keys_x.back().data, additional_tx_keys.back().data);
+ hw::ledger::check32("generate_output_ephemeral_keys", "additional_tx_key", additional_tx_public_keys_x.back().data, additional_tx_public_keys.back().data);
}
hw::ledger::check32("generate_output_ephemeral_keys", "out_eph_public_key", out_eph_public_key_x.data, out_eph_public_key.data);
#endif
@@ -1303,6 +1433,32 @@ namespace hw {
return true;
}
+ rct::key device_ledger::genCommitmentMask(const rct::key &AKout) {
+ #ifdef DEBUG_HWDEVICE
+ const rct::key AKout_x = hw::ledger::decrypt(AKout);
+ rct::key mask_x;
+ mask_x = this->controle_device->genCommitmentMask(AKout_x);
+ #endif
+
+ rct::key mask;
+ int offset = set_command_header_noopt(INS_GEN_COMMITMENT_MASK);
+ // AKout
+ memmove(this->buffer_send+offset, AKout.bytes, 32);
+ offset += 32;
+
+ this->buffer_send[4] = offset-5;
+ this->length_send = offset;
+ this->exchange();
+
+ memmove(mask.bytes, &this->buffer_recv[0], 32);
+
+ #ifdef DEBUG_HWDEVICE
+ hw::ledger::check32("genCommitmentMask", "mask", (const char*)mask_x.bytes, (const char*)mask.bytes);
+ #endif
+
+ return mask;
+ }
+
bool device_ledger::ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & AKout, bool short_amount) {
AUTO_LOCK_CMD();
@@ -1334,6 +1490,7 @@ namespace hw {
memmove(unmasked.mask.bytes, &this->buffer_recv[32], 32);
#ifdef DEBUG_HWDEVICE
+ MDEBUG("ecdhEncode: Akout: "<<AKout_x);
hw::ledger::check32("ecdhEncode", "amount", (char*)unmasked_x.amount.bytes, (char*)unmasked.amount.bytes);
hw::ledger::check32("ecdhEncode", "mask", (char*)unmasked_x.mask.bytes, (char*)unmasked.mask.bytes);
@@ -1374,6 +1531,7 @@ namespace hw {
memmove(masked.mask.bytes, &this->buffer_recv[32], 32);
#ifdef DEBUG_HWDEVICE
+ MDEBUG("ecdhEncode: Akout: "<<AKout_x);
hw::ledger::check32("ecdhDecode", "amount", (char*)masked_x.amount.bytes, (char*)masked.amount.bytes);
hw::ledger::check32("ecdhDecode", "mask", (char*)masked_x.mask.bytes,(char*) masked.mask.bytes);
#endif
diff --git a/src/device/device_ledger.hpp b/src/device/device_ledger.hpp
index 252354e1c..d4d98ce4a 100644
--- a/src/device/device_ledger.hpp
+++ b/src/device/device_ledger.hpp
@@ -33,6 +33,7 @@
#include <cstddef>
#include <string>
#include "device.hpp"
+#include "log.hpp"
#include "device_io_hid.hpp"
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
@@ -41,6 +42,18 @@ namespace hw {
namespace ledger {
+ /* Minimal supported version */
+ #define MINIMAL_APP_VERSION_MAJOR 1
+ #define MINIMAL_APP_VERSION_MINOR 3
+ #define MINIMAL_APP_VERSION_MICRO 1
+
+ #define VERSION(M,m,u) ((M)<<16|(m)<<8|(u))
+ #define VERSION_MAJOR(v) (((v)>>16)&0xFF)
+ #define VERSION_MINOR(v) (((v)>>8)&0xFF)
+ #define VERSION_MICRO(v) (((v)>>0)&0xFF)
+
+ #define MINIMAL_APP_VERSION VERSION(MINIMAL_APP_VERSION_MAJOR, MINIMAL_APP_VERSION_MINOR, MINIMAL_APP_VERSION_MICRO)
+
void register_all(std::map<std::string, std::unique_ptr<device>> &registry);
#ifdef WITH_DEVICE_LEDGER
@@ -190,11 +203,16 @@ namespace hw {
/* ======================================================================= */
/* TRANSACTION */
/* ======================================================================= */
-
+ void generate_tx_proof(const crypto::hash &prefix_hash,
+ const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
+ crypto::signature &sig) override;
+
bool open_tx(crypto::secret_key &tx_key) override;
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
+ rct::key genCommitmentMask(const rct::key &amount_key) override;
+
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_format) override;
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_format) override;
diff --git a/src/device/log.hpp b/src/device/log.hpp
index fb7ba1fb0..bfe6e9edc 100644
--- a/src/device/log.hpp
+++ b/src/device/log.hpp
@@ -40,6 +40,19 @@
namespace hw {
+ /* Note about debug:
+ * To debug Device you can def the following :
+ * #define DEBUG_HWDEVICE
+ * Activate debug mechanism:
+ * - Add more trace
+ * - All computation done by device are checked by default device.
+ * Required IODUMMYCRYPT_HWDEVICE or IONOCRYPT_HWDEVICE for fully working
+ * #define IODUMMYCRYPT_HWDEVICE 1
+ * - It assumes sensitive data encryption is is off on device side. a XOR with 0x55. This allow Ledger Class to make check on clear value
+ * #define IONOCRYPT_HWDEVICE 1
+ * - It assumes sensitive data encryption is off on device side.
+ */
+
void buffer_to_str(char *to_buff, size_t to_len, const char *buff, size_t len) ;
void log_hexbuffer(const std::string &msg, const char* buff, size_t len);
void log_message(const std::string &msg, const std::string &info );
diff --git a/src/p2p/net_node.inl b/src/p2p/net_node.inl
index 565fcf0ea..f0aef384f 100644
--- a/src/p2p/net_node.inl
+++ b/src/p2p/net_node.inl
@@ -502,7 +502,7 @@ namespace nodetool
else
{
memcpy(&m_network_id, &::config::NETWORK_ID, 16);
- if (m_exclusive_peers.empty())
+ if (m_exclusive_peers.empty() && !m_offline)
{
// for each hostname in the seed nodes list, attempt to DNS resolve and
// add the result addresses as seed nodes
diff --git a/src/ringct/rctSigs.cpp b/src/ringct/rctSigs.cpp
index 25571238e..e877c13ce 100644
--- a/src/ringct/rctSigs.cpp
+++ b/src/ringct/rctSigs.cpp
@@ -79,12 +79,12 @@ namespace
}
namespace rct {
- Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk)
+ Bulletproof proveRangeBulletproof(keyV &C, keyV &masks, const std::vector<uint64_t> &amounts, epee::span<const key> sk, hw::device &hwdev)
{
CHECK_AND_ASSERT_THROW_MES(amounts.size() == sk.size(), "Invalid amounts/sk sizes");
masks.resize(amounts.size());
for (size_t i = 0; i < masks.size(); ++i)
- masks[i] = genCommitmentMask(sk[i]);
+ masks[i] = hwdev.genCommitmentMask(sk[i]);
Bulletproof proof = bulletproof_PROVE(amounts, masks);
CHECK_AND_ASSERT_THROW_MES(proof.V.size() == amounts.size(), "V does not have the expected size");
C = proof.V;
@@ -804,7 +804,7 @@ namespace rct {
else
{
const epee::span<const key> keys{&amount_keys[0], amount_keys.size()};
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys));
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, outamounts, keys, hwdev));
#ifdef DBG
CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
#endif
@@ -833,7 +833,7 @@ namespace rct {
else
{
const epee::span<const key> keys{&amount_keys[amounts_proved], batch_size};
- rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys));
+ rv.p.bulletproofs.push_back(proveRangeBulletproof(C, masks, batch_amounts, keys, hwdev));
#ifdef DBG
CHECK_AND_ASSERT_THROW_MES(verBulletproof(rv.p.bulletproofs.back()), "verBulletproof failed on newly created proof");
#endif
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 91a8f6348..8974bd1e0 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -239,6 +239,9 @@ namespace
const char* USAGE_MARK_OUTPUT_SPENT("mark_output_spent <amount>/<offset> | <filename> [add]");
const char* USAGE_MARK_OUTPUT_UNSPENT("mark_output_unspent <amount>/<offset>");
const char* USAGE_IS_OUTPUT_SPENT("is_output_spent <amount>/<offset>");
+ const char* USAGE_FREEZE("freeze <key_image>");
+ const char* USAGE_THAW("thaw <key_image>");
+ const char* USAGE_FROZEN("frozen <key_image>");
const char* USAGE_VERSION("version");
const char* USAGE_HELP("help [<command>]");
@@ -2027,6 +2030,74 @@ bool simple_wallet::save_known_rings(const std::vector<std::string> &args)
return true;
}
+bool simple_wallet::freeze_thaw(const std::vector<std::string> &args, bool freeze)
+{
+ if (args.empty())
+ {
+ fail_msg_writer() << boost::format(tr("usage: %s <key_image>|<pubkey>")) % (freeze ? "freeze" : "thaw");
+ return true;
+ }
+ crypto::key_image ki;
+ if (!epee::string_tools::hex_to_pod(args[0], ki))
+ {
+ fail_msg_writer() << tr("failed to parse key image");
+ return true;
+ }
+ try
+ {
+ if (freeze)
+ m_wallet->freeze(ki);
+ else
+ m_wallet->thaw(ki);
+ }
+ catch (const std::exception &e)
+ {
+ fail_msg_writer() << e.what();
+ return true;
+ }
+
+ return true;
+}
+
+bool simple_wallet::freeze(const std::vector<std::string> &args)
+{
+ return freeze_thaw(args, true);
+}
+
+bool simple_wallet::thaw(const std::vector<std::string> &args)
+{
+ return freeze_thaw(args, false);
+}
+
+bool simple_wallet::frozen(const std::vector<std::string> &args)
+{
+ if (args.empty())
+ {
+ size_t ntd = m_wallet->get_num_transfer_details();
+ for (size_t i = 0; i < ntd; ++i)
+ {
+ if (!m_wallet->frozen(i))
+ continue;
+ const tools::wallet2::transfer_details &td = m_wallet->get_transfer_details(i);
+ message_writer() << tr("Frozen: ") << td.m_key_image << " " << cryptonote::print_money(td.amount());
+ }
+ }
+ else
+ {
+ crypto::key_image ki;
+ if (!epee::string_tools::hex_to_pod(args[0], ki))
+ {
+ fail_msg_writer() << tr("failed to parse key image");
+ return true;
+ }
+ if (m_wallet->frozen(ki))
+ message_writer() << tr("Frozen: ") << ki;
+ else
+ message_writer() << tr("Not frozen: ") << ki;
+ }
+ return true;
+}
+
bool simple_wallet::version(const std::vector<std::string> &args)
{
message_writer() << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")";
@@ -2602,7 +2673,7 @@ simple_wallet::simple_wallet()
tr(USAGE_INCOMING_TRANSFERS),
tr("Show the incoming transfers, all or filtered by availability and address index.\n\n"
"Output format:\n"
- "Amount, Spent(\"T\"|\"F\"), \"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] "));
+ "Amount, Spent(\"T\"|\"F\"), \"frozen\"|\"locked\"|\"unlocked\", RingCT, Global Index, Transaction Hash, Address Index, [Public Key, Key Image] "));
m_cmd_binder.set_handler("payments",
boost::bind(&simple_wallet::show_payments, this, _1),
tr(USAGE_PAYMENTS),
@@ -3014,6 +3085,18 @@ simple_wallet::simple_wallet()
boost::bind(&simple_wallet::blackballed, this, _1),
tr(USAGE_IS_OUTPUT_SPENT),
tr("Checks whether an output is marked as spent"));
+ m_cmd_binder.set_handler("freeze",
+ boost::bind(&simple_wallet::freeze, this, _1),
+ tr(USAGE_FREEZE),
+ tr("Freeze a single output by key image so it will not be used"));
+ m_cmd_binder.set_handler("thaw",
+ boost::bind(&simple_wallet::thaw, this, _1),
+ tr(USAGE_THAW),
+ tr("Thaw a single output by key image so it may be used again"));
+ m_cmd_binder.set_handler("frozen",
+ boost::bind(&simple_wallet::frozen, this, _1),
+ tr(USAGE_FROZEN),
+ tr("Checks whether a given output is currently frozen by key image"));
m_cmd_binder.set_handler("version",
boost::bind(&simple_wallet::version, this, _1),
tr(USAGE_VERSION),
@@ -4989,7 +5072,7 @@ bool simple_wallet::show_incoming_transfers(const std::vector<std::string>& args
boost::format("%21s%8s%12s%8s%16u%68s%16u%s") %
print_money(td.amount()) %
(td.m_spent ? tr("T") : tr("F")) %
- (m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) %
+ (m_wallet->frozen(td) ? tr("[frozen]") : m_wallet->is_transfer_unlocked(td) ? tr("unlocked") : tr("locked")) %
(td.is_rct() ? tr("RingCT") : tr("-")) %
td.m_global_output_index %
td.m_txid %
@@ -6873,11 +6956,6 @@ bool simple_wallet::set_tx_key(const std::vector<std::string> &args_)
//----------------------------------------------------------------------------------------------------
bool simple_wallet::get_tx_proof(const std::vector<std::string> &args)
{
- if (m_wallet->key_on_device() && m_wallet->get_account().get_device().get_type() != hw::device::TREZOR)
- {
- fail_msg_writer() << tr("command not supported by HW wallet");
- return true;
- }
if (args.size() != 2 && args.size() != 3)
{
PRINT_USAGE(USAGE_GET_TX_PROOF);
diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h
index 7bcb92190..c9a5c55e8 100644
--- a/src/simplewallet/simplewallet.h
+++ b/src/simplewallet/simplewallet.h
@@ -238,9 +238,12 @@ namespace cryptonote
bool blackball(const std::vector<std::string>& args);
bool unblackball(const std::vector<std::string>& args);
bool blackballed(const std::vector<std::string>& args);
+ bool freeze(const std::vector<std::string>& args);
+ bool thaw(const std::vector<std::string>& args);
+ bool frozen(const std::vector<std::string>& args);
bool version(const std::vector<std::string>& args);
- bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func);
+ bool cold_sign_tx(const std::vector<tools::wallet2::pending_tx>& ptx_vector, tools::wallet2::signed_tx_set &exported_txs, std::vector<cryptonote::address_parse_info> &dsts_info, std::function<bool(const tools::wallet2::signed_tx_set &)> accept_func);
uint64_t get_daemon_blockchain_height(std::string& err);
bool try_connect_to_daemon(bool silent = false, uint32_t* version = nullptr);
bool ask_wallet_create_if_needed();
@@ -253,6 +256,7 @@ namespace cryptonote
void key_images_sync_intern();
void on_refresh_finished(uint64_t start_height, uint64_t fetched_blocks, bool is_init, bool received_money);
std::pair<std::string, std::string> show_outputs_line(const std::vector<uint64_t> &heights, uint64_t blockchain_height, uint64_t highlight_height = std::numeric_limits<uint64_t>::max()) const;
+ bool freeze_thaw(const std::vector<std::string>& args, bool freeze);
struct transfer_view
{
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 3f041105d..0e82f1a91 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -1449,6 +1449,58 @@ void wallet2::set_unspent(size_t idx)
td.m_spent_height = 0;
}
//----------------------------------------------------------------------------------------------------
+void wallet2::freeze(size_t idx)
+{
+ CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index");
+ transfer_details &td = m_transfers[idx];
+ td.m_frozen = true;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::thaw(size_t idx)
+{
+ CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index");
+ transfer_details &td = m_transfers[idx];
+ td.m_frozen = false;
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::frozen(size_t idx) const
+{
+ CHECK_AND_ASSERT_THROW_MES(idx < m_transfers.size(), "Invalid transfer_details index");
+ const transfer_details &td = m_transfers[idx];
+ return td.m_frozen;
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::freeze(const crypto::key_image &ki)
+{
+ freeze(get_transfer_details(ki));
+}
+//----------------------------------------------------------------------------------------------------
+void wallet2::thaw(const crypto::key_image &ki)
+{
+ thaw(get_transfer_details(ki));
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::frozen(const crypto::key_image &ki) const
+{
+ return frozen(get_transfer_details(ki));
+}
+//----------------------------------------------------------------------------------------------------
+size_t wallet2::get_transfer_details(const crypto::key_image &ki) const
+{
+ for (size_t idx = 0; idx < m_transfers.size(); ++idx)
+ {
+ const transfer_details &td = m_transfers[idx];
+ if (td.m_key_image_known && td.m_key_image == ki)
+ return idx;
+ }
+ CHECK_AND_ASSERT_THROW_MES(false, "Key image not found");
+}
+//----------------------------------------------------------------------------------------------------
+bool wallet2::frozen(const transfer_details &td) const
+{
+ return td.m_frozen;
+}
+//----------------------------------------------------------------------------------------------------
void wallet2::check_acc_out_precomp(const tx_out &o, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, size_t i, tx_scan_info_t &tx_scan_info) const
{
hw::device &hwdev = m_account.get_device();
@@ -1866,6 +1918,7 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
td.m_mask = rct::identity();
td.m_rct = false;
}
+ td.m_frozen = false;
set_unspent(m_transfers.size()-1);
if (td.m_key_image_known)
m_key_images[td.m_key_image] = m_transfers.size()-1;
@@ -3213,8 +3266,9 @@ void wallet2::detach_blockchain(uint64_t height, std::map<std::pair<uint64_t, ui
wallet2::transfer_details &td = m_transfers[i];
if (td.m_spent && td.m_spent_height >= height)
{
- LOG_PRINT_L1("Resetting spent status for output " << i << ": " << td.m_key_image);
+ LOG_PRINT_L1("Resetting spent/frozen status for output " << i << ": " << td.m_key_image);
set_unspent(i);
+ thaw(i);
}
}
@@ -5402,7 +5456,7 @@ std::map<uint32_t, uint64_t> wallet2::balance_per_subaddress(uint32_t index_majo
std::map<uint32_t, uint64_t> amount_per_subaddr;
for (const auto& td: m_transfers)
{
- if (td.m_subaddr_index.major == index_major && !td.m_spent)
+ if (td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen)
{
auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
if (found == amount_per_subaddr.end())
@@ -5431,7 +5485,7 @@ std::map<uint32_t, uint64_t> wallet2::unlocked_balance_per_subaddress(uint32_t i
std::map<uint32_t, uint64_t> amount_per_subaddr;
for(const transfer_details& td: m_transfers)
{
- if(td.m_subaddr_index.major == index_major && !td.m_spent && is_transfer_unlocked(td))
+ if(td.m_subaddr_index.major == index_major && !td.m_spent && !td.m_frozen && is_transfer_unlocked(td))
{
auto found = amount_per_subaddr.find(td.m_subaddr_index.minor);
if (found == amount_per_subaddr.end())
@@ -8325,7 +8379,7 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_frozen && td.is_rct() && td.amount() >= needed_money && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("We can use " << i << " alone: " << print_money(td.amount()));
picks.push_back(i);
@@ -8340,13 +8394,13 @@ std::vector<size_t> wallet2::pick_preferred_rct_inputs(uint64_t needed_money, ui
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && td.is_rct() && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
LOG_PRINT_L2("Considering input " << i << ", " << print_money(td.amount()));
for (size_t j = i + 1; j < m_transfers.size(); ++j)
{
const transfer_details& td2 = m_transfers[j];
- if (!td2.m_spent && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
+ if (!td2.m_spent && !td2.m_frozen && !td.m_key_image_partial && td2.is_rct() && td.amount() + td2.amount() >= needed_money && is_transfer_unlocked(td2) && td2.m_subaddr_index == td.m_subaddr_index)
{
// update our picks if those outputs are less related than any we
// already found. If the same, don't update, and oldest suitable outputs
@@ -8573,6 +8627,7 @@ void wallet2::light_wallet_get_unspent_outs()
td.m_pk_index = 0;
td.m_internal_output_index = o.index;
td.m_spent = spent;
+ td.m_frozen = false;
tx_out txout;
txout.target = txout_to_key(public_key);
@@ -9047,7 +9102,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_2(std::vector<cryp
MDEBUG("Ignoring output " << i << " of amount " << print_money(td.amount()) << " which is below threshold " << print_money(fractional_threshold));
continue;
}
- if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
+ if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && subaddr_indices.count(td.m_subaddr_index.minor) == 1)
{
const uint32_t index_minor = td.m_subaddr_index.minor;
auto find_predicate = [&index_minor](const std::pair<uint32_t, std::vector<size_t>>& x) { return x.first == index_minor; };
@@ -9479,7 +9534,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;
@@ -9527,7 +9582,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_all(uint64_t below
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (!td.m_spent && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1))
+ if (!td.m_spent && !td.m_frozen && !td.m_key_image_partial && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td) && td.m_subaddr_index.major == subaddr_account && (subaddr_indices.empty() || subaddr_indices.count(td.m_subaddr_index.minor) == 1))
{
fund_found = true;
if (below == 0 || td.amount() < below)
@@ -9575,7 +9630,7 @@ std::vector<wallet2::pending_tx> wallet2::create_transactions_single(const crypt
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details& td = m_transfers[i];
- if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
+ if (td.m_key_image_known && td.m_key_image == ki && !td.m_spent && !td.m_frozen && (use_rct ? true : !td.is_rct()) && is_transfer_unlocked(td))
{
if (td.is_rct() || is_valid_decomposed_amount(td.amount()))
unused_transfers_indices.push_back(i);
@@ -9795,8 +9850,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");
@@ -9907,6 +9968,8 @@ std::vector<size_t> wallet2::select_available_outputs(const std::function<bool(c
{
if (i->m_spent)
continue;
+ if (i->m_frozen)
+ continue;
if (i->m_key_image_partial)
continue;
if (!is_transfer_unlocked(*i))
@@ -9922,7 +9985,7 @@ std::vector<uint64_t> wallet2::get_unspent_amounts_vector() const
std::set<uint64_t> set;
for (const auto &td: m_transfers)
{
- if (!td.m_spent)
+ if (!td.m_spent && !td.m_frozen)
set.insert(td.is_rct() ? 0 : td.amount());
}
std::vector<uint64_t> vector;
@@ -10048,7 +10111,7 @@ void wallet2::discard_unmixable_outputs()
std::vector<size_t> unmixable_outputs = select_available_unmixable_outputs();
for (size_t idx : unmixable_outputs)
{
- m_transfers[idx].m_spent = true;
+ freeze(idx);
}
}
@@ -10435,7 +10498,7 @@ void wallet2::check_tx_key(const crypto::hash &txid, const crypto::secret_key &t
void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypto::key_derivation &derivation, const std::vector<crypto::key_derivation> &additional_derivations, const cryptonote::account_public_address &address, uint64_t &received) const
{
received = 0;
- hw::device &hwdev = m_account.get_device();
+
for (size_t n = 0; n < tx.vout.size(); ++n)
{
const cryptonote::txout_to_key* const out_key = boost::get<cryptonote::txout_to_key>(std::addressof(tx.vout[n].target));
@@ -10443,13 +10506,13 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
continue;
crypto::public_key derived_out_key;
- bool r = hwdev.derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key);
+ bool r = crypto::derive_public_key(derivation, n, address.m_spend_public_key, derived_out_key);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key");
bool found = out_key->key == derived_out_key;
crypto::key_derivation found_derivation = derivation;
if (!found && !additional_derivations.empty())
{
- r = hwdev.derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key);
+ r = crypto::derive_public_key(additional_derivations[n], n, address.m_spend_public_key, derived_out_key);
THROW_WALLET_EXCEPTION_IF(!r, error::wallet_internal_error, "Failed to derive public key");
found = out_key->key == derived_out_key;
found_derivation = additional_derivations[n];
@@ -10465,9 +10528,9 @@ void wallet2::check_tx_key_helper(const cryptonote::transaction &tx, const crypt
else
{
crypto::secret_key scalar1;
- hwdev.derivation_to_scalar(found_derivation, n, scalar1);
+ crypto::derivation_to_scalar(found_derivation, n, scalar1);
rct::ecdhTuple ecdh_info = tx.rct_signatures.ecdhInfo[n];
- hwdev.ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
+ rct::ecdhDecode(ecdh_info, rct::sk2rct(scalar1), tx.rct_signatures.type == rct::RCTTypeBulletproof2);
const rct::key C = tx.rct_signatures.outPk[n].mask;
rct::key Ctmp;
THROW_WALLET_EXCEPTION_IF(sc_check(ecdh_info.mask.bytes) != 0, error::wallet_internal_error, "Bad ECDH input mask");
@@ -10578,6 +10641,8 @@ std::string wallet2::get_tx_proof(const crypto::hash &txid, const cryptonote::ac
std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypto::secret_key &tx_key, const std::vector<crypto::secret_key> &additional_tx_keys, const cryptonote::account_public_address &address, bool is_subaddress, const std::string &message) const
{
+ hw::device &hwdev = m_account.get_device();
+ rct::key aP;
// determine if the address is found in the subaddress hash table (i.e. whether the proof is outbound or inbound)
const bool is_out = m_subaddresses.count(address.m_spend_public_key) == 0;
@@ -10596,30 +10661,34 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
shared_secret.resize(num_sigs);
sig.resize(num_sigs);
- shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key)));
+ hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(tx_key));
+ shared_secret[0] = rct::rct2pk(aP);
crypto::public_key tx_pub_key;
if (is_subaddress)
{
- tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key)));
- crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]);
+ hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(tx_key));
+ tx_pub_key = rct2pk(aP);
+ hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[0], tx_key, sig[0]);
}
else
{
- crypto::secret_key_to_public_key(tx_key, tx_pub_key);
- crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
+ hwdev.secret_key_to_public_key(tx_key, tx_pub_key);
+ hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[0], tx_key, sig[0]);
}
for (size_t i = 1; i < num_sigs; ++i)
{
- shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1])));
+ hwdev.scalarmultKey(aP, rct::pk2rct(address.m_view_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
+ shared_secret[i] = rct::rct2pk(aP);
if (is_subaddress)
{
- tx_pub_key = rct2pk(rct::scalarmultKey(rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1])));
- crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
+ hwdev.scalarmultKey(aP, rct::pk2rct(address.m_spend_public_key), rct::sk2rct(additional_tx_keys[i - 1]));
+ tx_pub_key = rct2pk(aP);
+ hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, address.m_spend_public_key, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
else
{
- crypto::secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key);
- crypto::generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
+ hwdev.secret_key_to_public_key(additional_tx_keys[i - 1], tx_pub_key);
+ hwdev.generate_tx_proof(prefix_hash, tx_pub_key, address.m_view_public_key, boost::none, shared_secret[i], additional_tx_keys[i - 1], sig[i]);
}
}
sig_str = std::string("OutProofV1");
@@ -10635,25 +10704,27 @@ std::string wallet2::get_tx_proof(const cryptonote::transaction &tx, const crypt
sig.resize(num_sigs);
const crypto::secret_key& a = m_account.get_keys().m_view_secret_key;
- shared_secret[0] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(tx_pub_key), rct::sk2rct(a)));
+ hwdev.scalarmultKey(aP, rct::pk2rct(tx_pub_key), rct::sk2rct(a));
+ shared_secret[0] = rct2pk(aP);
if (is_subaddress)
{
- crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]);
+ hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, address.m_spend_public_key, shared_secret[0], a, sig[0]);
}
else
{
- crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]);
+ hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, tx_pub_key, boost::none, shared_secret[0], a, sig[0]);
}
for (size_t i = 1; i < num_sigs; ++i)
{
- shared_secret[i] = rct::rct2pk(rct::scalarmultKey(rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a)));
+ hwdev.scalarmultKey(aP,rct::pk2rct(additional_tx_pub_keys[i - 1]), rct::sk2rct(a));
+ shared_secret[i] = rct2pk(aP);
if (is_subaddress)
{
- crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]);
+ hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], address.m_spend_public_key, shared_secret[i], a, sig[i]);
}
else
{
- crypto::generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]);
+ hwdev.generate_tx_proof(prefix_hash, address.m_view_public_key, additional_tx_pub_keys[i - 1], boost::none, shared_secret[i], a, sig[i]);
}
}
sig_str = std::string("InProofV1");
@@ -10831,7 +10902,7 @@ std::string wallet2::get_reserve_proof(const boost::optional<std::pair<uint32_t,
for (size_t i = 0; i < m_transfers.size(); ++i)
{
const transfer_details &td = m_transfers[i];
- if (!td.m_spent && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major))
+ if (!td.m_spent && !td.m_frozen && (!account_minreserve || account_minreserve->first == td.m_subaddr_index.major))
selected_transfers.push_back(i);
}
@@ -11578,6 +11649,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
for(size_t i = 0; i < offset; ++i)
{
const transfer_details &td = m_transfers[i];
+ if (td.m_frozen)
+ continue;
uint64_t amount = td.amount();
if (td.m_spent)
spent += amount;
@@ -11589,6 +11662,8 @@ uint64_t wallet2::import_key_images(const std::vector<std::pair<crypto::key_imag
for(size_t i = 0; i < signed_key_images.size(); ++i)
{
const transfer_details &td = m_transfers[i + offset];
+ if (td.m_frozen)
+ continue;
uint64_t amount = td.amount();
if (td.m_spent)
spent += amount;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 2bc11d2f7..2eb7de94a 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -266,6 +266,7 @@ namespace tools
size_t m_internal_output_index;
uint64_t m_global_output_index;
bool m_spent;
+ bool m_frozen;
uint64_t m_spent_height;
crypto::key_image m_key_image; //TODO: key_image stored twice :(
rct::key m_mask;
@@ -291,6 +292,7 @@ namespace tools
FIELD(m_internal_output_index)
FIELD(m_global_output_index)
FIELD(m_spent)
+ FIELD(m_frozen)
FIELD(m_spent_height)
FIELD(m_key_image)
FIELD(m_mask)
@@ -1252,6 +1254,14 @@ namespace tools
bool unblackball_output(const std::pair<uint64_t, uint64_t> &output);
bool is_output_blackballed(const std::pair<uint64_t, uint64_t> &output) const;
+ void freeze(size_t idx);
+ void thaw(size_t idx);
+ bool frozen(size_t idx) const;
+ void freeze(const crypto::key_image &ki);
+ void thaw(const crypto::key_image &ki);
+ bool frozen(const crypto::key_image &ki) const;
+ bool frozen(const transfer_details &td) const;
+
// MMS -------------------------------------------------------------------------------------------------
mms::message_store& get_message_store() { return m_message_store; };
const mms::message_store& get_message_store() const { return m_message_store; };
@@ -1334,6 +1344,7 @@ namespace tools
bool get_ring(const crypto::chacha_key &key, const crypto::key_image &key_image, std::vector<uint64_t> &outs);
crypto::chacha_key get_ringdb_key();
void setup_keys(const epee::wipeable_string &password);
+ size_t get_transfer_details(const crypto::key_image &ki) const;
void register_devices();
hw::device& lookup_device(const std::string & device_descriptor);
@@ -1488,7 +1499,7 @@ namespace tools
};
}
BOOST_CLASS_VERSION(tools::wallet2, 28)
-BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 11)
+BOOST_CLASS_VERSION(tools::wallet2::transfer_details, 12)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info, 1)
BOOST_CLASS_VERSION(tools::wallet2::multisig_info::LR, 0)
BOOST_CLASS_VERSION(tools::wallet2::multisig_tx_set, 1)
@@ -1550,6 +1561,10 @@ namespace boost
{
x.m_key_image_request = false;
}
+ if (ver < 12)
+ {
+ x.m_frozen = false;
+ }
}
template <class Archive>
@@ -1638,8 +1653,17 @@ namespace boost
}
a & x.m_key_image_request;
if (ver < 11)
+ {
+ initialize_transfer_details(a, x, ver);
return;
+ }
a & x.m_uses;
+ if (ver < 12)
+ {
+ initialize_transfer_details(a, x, ver);
+ return;
+ }
+ a & x.m_frozen;
}
template <class Archive>
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/block_weight/block_weight.py b/tests/block_weight/block_weight.py
index d6fd494e3..ba533c53c 100755
--- a/tests/block_weight/block_weight.py
+++ b/tests/block_weight/block_weight.py
@@ -3,6 +3,7 @@
# This uses the scheme proposed by ArticMine
# Written by Sarang Nother
# Copyright (c) 2019 The Monero Project
+from __future__ import print_function
import sys
import math
@@ -67,7 +68,7 @@ def run(t, blocks):
lt_weights.append(min(max_weight,int(ltembw + int(ltembw * 2 / 5))))
#print "H %u, r %u, BW %u, EMBW %u, LTBW %u, LTEMBW %u, ltmedian %u" % (block, r, max_weight, embw, lt_weights[-1], ltembw, ltmedian)
- print "H %u, BW %u, EMBW %u, LTBW %u" % (block, max_weight, embw, lt_weights[-1])
+ print("H %u, BW %u, EMBW %u, LTBW %u" % (block, max_weight, embw, lt_weights[-1]))
run(0, 2 * MEDIAN_WINDOW_BIG)
run(1, 9 * MEDIAN_WINDOW_BIG)
diff --git a/tests/block_weight/compare.py b/tests/block_weight/compare.py
index e058a7079..e046e1640 100755
--- a/tests/block_weight/compare.py
+++ b/tests/block_weight/compare.py
@@ -1,5 +1,6 @@
-#!/usr/bin/python
+#!/usr/bin/env python
+from __future__ import print_function
import sys
import subprocess
@@ -10,11 +11,11 @@ else:
first = [sys.argv[1]]
second = [sys.argv[2]]
-print 'running: ', first
+print('running: ', first)
S0 = subprocess.check_output(first, stderr=subprocess.STDOUT)
-print 'running: ', second
+print('running: ', second)
S1 = subprocess.check_output(second, stderr=subprocess.STDOUT)
-print 'comparing'
+print('comparing')
if S0 != S1:
sys.exit(1)
sys.exit(0)
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/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)