mirror of
https://github.com/oxen-io/oxen-core.git
synced 2023-12-14 02:22:56 +01:00
Merge commit '4308a2e' into LokiMergeUpstream
This commit is contained in:
commit
9d1df98f37
|
@ -129,6 +129,7 @@ Blockchain::Blockchain(tx_memory_pool& tx_pool, service_nodes::service_node_list
|
|||
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_service_node_list(service_node_list),
|
||||
|
@ -1292,7 +1293,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
|
||||
|
|
|
@ -1111,6 +1111,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;
|
||||
|
|
|
@ -204,6 +204,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
|
||||
|
|
|
@ -76,4 +76,6 @@ target_link_libraries(device
|
|||
${OPENSSL_CRYPTO_LIBRARIES}
|
||||
${Boost_SERIALIZATION_LIBRARY}
|
||||
PRIVATE
|
||||
version
|
||||
${Blocks}
|
||||
${EXTRA_LIBRARIES})
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
@ -350,6 +354,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;
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -28,8 +28,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"
|
||||
|
@ -174,6 +174,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
|
||||
|
@ -181,6 +182,7 @@ namespace hw {
|
|||
#define INS_MLSAG 0x7E
|
||||
#define INS_CLOSE_TX 0x80
|
||||
|
||||
#define INS_GET_TX_PROOF 0xA0
|
||||
|
||||
#define INS_GET_RESPONSE 0xc0
|
||||
|
||||
|
@ -303,8 +305,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) {
|
||||
|
@ -315,9 +333,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;
|
||||
}
|
||||
|
||||
|
@ -791,7 +809,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);
|
||||
|
@ -826,6 +848,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);
|
||||
|
@ -837,6 +864,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
|
||||
|
@ -851,7 +881,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);
|
||||
|
@ -867,7 +897,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);
|
||||
|
@ -886,11 +915,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
|
||||
|
||||
|
@ -1051,7 +1080,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
|
||||
|
||||
|
@ -1113,6 +1142,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();
|
||||
|
||||
|
@ -1131,7 +1229,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;
|
||||
}
|
||||
|
||||
|
@ -1142,7 +1244,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);
|
||||
|
@ -1179,31 +1285,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::tx_destination_entry> &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::tx_destination_entry> 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;
|
||||
bool is_change_x;
|
||||
this->controle_device->generate_output_ephemeral_keys(tx_version_x, is_change_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,
|
||||
|
||||
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 = found_change; // NOTE(loki): Alias our param into theirs so we don't have to change much code.
|
||||
|
||||
|
@ -1229,8 +1362,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);
|
||||
|
@ -1245,6 +1381,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
|
||||
|
@ -1253,6 +1390,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;
|
||||
|
@ -1260,15 +1404,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;
|
||||
|
@ -1280,6 +1417,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,
|
||||
|
@ -1287,9 +1434,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
|
||||
|
@ -1304,6 +1452,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();
|
||||
|
||||
|
@ -1335,6 +1509,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);
|
||||
|
||||
|
@ -1375,6 +1550,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
|
||||
|
|
|
@ -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>> ®istry);
|
||||
|
||||
#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;
|
||||
|
||||
|
|
|
@ -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 );
|
||||
|
|
|
@ -495,7 +495,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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -250,6 +250,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>]");
|
||||
|
||||
|
@ -2038,6 +2041,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() << "Loki '" << LOKI_RELEASE_NAME << "' (v" << LOKI_VERSION_FULL << ")";
|
||||
|
@ -2565,7 +2636,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),
|
||||
|
@ -2976,6 +3047,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),
|
||||
|
@ -4975,7 +5058,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 %
|
||||
|
@ -7090,11 +7173,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);
|
||||
|
|
|
@ -246,8 +246,10 @@ 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 register_service_node_main(
|
||||
const std::vector<std::string>& service_node_key_as_str,
|
||||
|
@ -259,6 +261,7 @@ namespace cryptonote
|
|||
uint64_t bc_height,
|
||||
uint64_t staking_requirement);
|
||||
|
||||
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();
|
||||
|
@ -271,6 +274,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
|
||||
{
|
||||
|
|
|
@ -1438,6 +1438,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();
|
||||
|
@ -1874,7 +1926,8 @@ void wallet2::process_new_transaction(const crypto::hash &txid, const cryptonote
|
|||
td.m_mask = rct::identity();
|
||||
td.m_rct = false;
|
||||
}
|
||||
set_unspent(m_transfers.size()-1);
|
||||
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;
|
||||
m_pub_keys[tx_scan_info[o].in_ephemeral.pub] = m_transfers.size()-1;
|
||||
|
@ -3327,8 +3380,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5513,7 +5567,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())
|
||||
|
@ -5542,7 +5596,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())
|
||||
|
@ -9094,8 +9148,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);
|
||||
|
@ -9110,13 +9163,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
|
||||
|
@ -9343,6 +9396,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);
|
||||
|
@ -9821,7 +9875,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; };
|
||||
|
@ -10254,7 +10308,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;
|
||||
|
@ -10302,7 +10356,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)
|
||||
|
@ -10353,7 +10407,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);
|
||||
|
@ -10572,8 +10626,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");
|
||||
|
||||
|
@ -10687,6 +10747,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))
|
||||
|
@ -10702,7 +10764,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;
|
||||
|
@ -10830,7 +10892,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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11217,7 +11279,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));
|
||||
|
@ -11225,13 +11287,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];
|
||||
|
@ -11247,9 +11309,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");
|
||||
|
@ -11360,6 +11422,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;
|
||||
|
||||
|
@ -11378,30 +11442,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");
|
||||
|
@ -11417,25 +11485,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");
|
||||
|
@ -11613,7 +11683,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);
|
||||
}
|
||||
|
||||
|
@ -12353,6 +12423,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;
|
||||
|
@ -12364,6 +12436,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;
|
||||
|
|
|
@ -309,6 +309,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;
|
||||
|
@ -334,6 +335,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)
|
||||
|
@ -1376,6 +1378,14 @@ namespace tools
|
|||
};
|
||||
request_stake_unlock_result can_request_stake_unlock(const crypto::public_key &sn_key);
|
||||
|
||||
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; };
|
||||
|
@ -1458,6 +1468,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);
|
||||
|
@ -1626,7 +1637,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)
|
||||
|
@ -1688,6 +1699,10 @@ namespace boost
|
|||
{
|
||||
x.m_key_image_request = false;
|
||||
}
|
||||
if (ver < 12)
|
||||
{
|
||||
x.m_frozen = false;
|
||||
}
|
||||
}
|
||||
|
||||
template <class Archive>
|
||||
|
@ -1776,8 +1791,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>
|
||||
|
|
|
@ -85,6 +85,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;
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
#!/usr/bin/python
|
||||
#!/usr/bin/env python
|
||||
|
||||
from __future__ import print_function
|
||||
import sys
|
||||
import subprocess
|
||||
|
||||
print 'running: ', sys.argv[1]
|
||||
S0 = subprocess.check_output(sys.argv[1], stderr=subprocess.STDOUT)
|
||||
print 'running: ', sys.argv[2]
|
||||
S1 = subprocess.check_output(sys.argv[2], stderr=subprocess.STDOUT)
|
||||
print 'comparing'
|
||||
if len(sys.argv) == 4:
|
||||
first = [sys.argv[1], sys.argv[2]]
|
||||
second = [sys.argv[3]]
|
||||
else:
|
||||
first = [sys.argv[1]]
|
||||
second = [sys.argv[2]]
|
||||
|
||||
print('running: ', first)
|
||||
S0 = subprocess.check_output(first, stderr=subprocess.STDOUT)
|
||||
print('running: ', second)
|
||||
S1 = subprocess.check_output(second, stderr=subprocess.STDOUT)
|
||||
print('comparing')
|
||||
if S0 != S1:
|
||||
sys.exit(1)
|
||||
sys.exit(0)
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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__':
|
||||
|
|
146
tests/functional_tests/cold_signing.py
Executable file
146
tests/functional_tests/cold_signing.py
Executable file
|
@ -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()
|
89
tests/functional_tests/daemon_info.py
Executable file
89
tests/functional_tests/daemon_info.py
Executable file
|
@ -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()
|
135
tests/functional_tests/functional_tests_rpc.py
Executable file
135
tests/functional_tests/functional_tests_rpc.py
Executable file
|
@ -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, ', '))
|
101
tests/functional_tests/integrated_address.py
Executable file
101
tests/functional_tests/integrated_address.py
Executable file
|
@ -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()
|
123
tests/functional_tests/mining.py
Executable file
123
tests/functional_tests/mining.py
Executable file
|
@ -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()
|
227
tests/functional_tests/multisig.py
Executable file
227
tests/functional_tests/multisig.py
Executable file
|
@ -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()
|
282
tests/functional_tests/proofs.py
Executable file
282
tests/functional_tests/proofs.py
Executable file
|
@ -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()
|
85
tests/functional_tests/sign_message.py
Executable file
85
tests/functional_tests/sign_message.py
Executable file
|
@ -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()
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
|
@ -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)
|
487
tests/functional_tests/transfer.py
Executable file
487
tests/functional_tests/transfer.py
Executable file
|
@ -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()
|
156
tests/functional_tests/txpool.py
Executable file
156
tests/functional_tests/txpool.py
Executable file
|
@ -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()
|
152
tests/functional_tests/wallet_address.py
Executable file
152
tests/functional_tests/wallet_address.py
Executable file
|
@ -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()
|
49
utils/python-rpc/console.py
Executable file
49
utils/python-rpc/console.py
Executable file
|
@ -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')
|
219
utils/python-rpc/framework/daemon.py
Normal file
219
utils/python-rpc/framework/daemon.py
Normal file
|
@ -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)
|
|
@ -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')
|
||||
|
||||
|
||||
|
600
utils/python-rpc/framework/wallet.py
Normal file
600
utils/python-rpc/framework/wallet.py
Normal file
|
@ -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)
|
Loading…
Reference in a new issue