Merge commit '4308a2e' into LokiMergeUpstream

This commit is contained in:
Doyle 2019-04-12 18:00:11 +10:00
commit 9d1df98f37
40 changed files with 3575 additions and 399 deletions

View file

@ -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

View file

@ -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;

View file

@ -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

View file

@ -76,4 +76,6 @@ target_link_libraries(device
${OPENSSL_CRYPTO_LIBRARIES}
${Boost_SERIALIZATION_LIBRARY}
PRIVATE
version
${Blocks}
${EXTRA_LIBRARIES})

View file

@ -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;

View file

@ -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;

View file

@ -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;

View file

@ -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

View file

@ -33,6 +33,7 @@
#include <cstddef>
#include <string>
#include "device.hpp"
#include "log.hpp"
#include "device_io_hid.hpp"
#include <boost/thread/mutex.hpp>
#include <boost/thread/recursive_mutex.hpp>
@ -41,6 +42,18 @@ namespace hw {
namespace ledger {
/* Minimal supported version */
#define MINIMAL_APP_VERSION_MAJOR 1
#define MINIMAL_APP_VERSION_MINOR 3
#define MINIMAL_APP_VERSION_MICRO 1
#define VERSION(M,m,u) ((M)<<16|(m)<<8|(u))
#define VERSION_MAJOR(v) (((v)>>16)&0xFF)
#define VERSION_MINOR(v) (((v)>>8)&0xFF)
#define VERSION_MICRO(v) (((v)>>0)&0xFF)
#define MINIMAL_APP_VERSION VERSION(MINIMAL_APP_VERSION_MAJOR, MINIMAL_APP_VERSION_MINOR, MINIMAL_APP_VERSION_MICRO)
void register_all(std::map<std::string, std::unique_ptr<device>> &registry);
#ifdef WITH_DEVICE_LEDGER
@ -190,11 +203,16 @@ namespace hw {
/* ======================================================================= */
/* TRANSACTION */
/* ======================================================================= */
void generate_tx_proof(const crypto::hash &prefix_hash,
const crypto::public_key &R, const crypto::public_key &A, const boost::optional<crypto::public_key> &B, const crypto::public_key &D, const crypto::secret_key &r,
crypto::signature &sig) override;
bool open_tx(crypto::secret_key &tx_key) override;
bool encrypt_payment_id(crypto::hash8 &payment_id, const crypto::public_key &public_key, const crypto::secret_key &secret_key) override;
rct::key genCommitmentMask(const rct::key &amount_key) override;
bool ecdhEncode(rct::ecdhTuple & unmasked, const rct::key & sharedSec, bool short_format) override;
bool ecdhDecode(rct::ecdhTuple & masked, const rct::key & sharedSec, bool short_format) override;

View file

@ -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 );

View file

@ -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

View file

@ -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

View file

@ -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);

View file

@ -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
{

View file

@ -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;

View file

@ -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>

View file

@ -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;

View file

@ -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)

View file

@ -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)

View file

@ -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)

View file

@ -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__':

View 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()

View 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()

View 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, ', '))

View 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
View 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()

View 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
View 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()

View 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()

View file

@ -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)

View file

@ -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)

View file

@ -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)

View 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
View 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()

View 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
View 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')

View 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)

View file

@ -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')

View 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)