diff --git a/README.md b/README.md index 8048a9710..96ab39e2c 100644 --- a/README.md +++ b/README.md @@ -95,7 +95,8 @@ Dates are provided in the format YYYY-MM-DD. | ------------------------------ | -----------| ----------------- | ---------------------- | -------------------------- | ---------------------------------------------------------------------------------- | | 1 | 2018-04-01 | v7 | v0.1.0.0 | v0.1.0.0 | Cryptonight variant 1, ringsize >= 8, sorted inputs | 6969 | 2018-04-24 | v8 | v0.2.0.0 | v0.2.0.0 | Bulletproofs, LWMA difficulty algorithm, ringsize >= 10, reduce unlock to 4 -| 53666 | 2018-10-06 | v9 | v0.3.0.0 | v0.3.0.1 | Cryptonight variant 2, LWMA v2, ringsize = 22, XXX +| 53666 | 2018-10-06 | v9 | v0.3.0.0 | v0.3.1.3 | Cryptonight variant 2, LWMA v2, ringsize = 22, MMS +| 63469 | 2018-11-11 | v10 | v0.4.0.0 | v0.4.0.0 | LWMA v4 X's indicate that these details have not been determined as of commit date. diff --git a/contrib/epee/include/net/http_server_handlers_map2.h b/contrib/epee/include/net/http_server_handlers_map2.h index 429e3e1af..00a867d3e 100644 --- a/contrib/epee/include/net/http_server_handlers_map2.h +++ b/contrib/epee/include/net/http_server_handlers_map2.h @@ -122,6 +122,7 @@ if(!ps.load_from_json(query_info.m_body)) \ { \ boost::value_initialized rsp; \ + static_cast(rsp).jsonrpc = "2.0"; \ static_cast(rsp).error.code = -32700; \ static_cast(rsp).error.message = "Parse error"; \ epee::serialization::store_t_to_json(static_cast(rsp), response_info.m_body); \ diff --git a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h index 7087136cc..db4b1af5e 100644 --- a/contrib/epee/include/serialization/keyvalue_serialization_overloads.h +++ b/contrib/epee/include/serialization/keyvalue_serialization_overloads.h @@ -35,6 +35,11 @@ namespace epee { + namespace + { + template void hint_resize(C &container, size_t size) {} + template void hint_resize(std::vector &container, size_t size) { container.reserve(size); } + } namespace serialization { @@ -158,6 +163,7 @@ namespace epee false, "size in blob " << loaded_size << " not have not zero modulo for sizeof(value_type) = " << sizeof(typename stl_container::value_type)); size_t count = (loaded_size/sizeof(typename stl_container::value_type)); + hint_resize(container, count); for(size_t i = 0; i < count; i++) container.insert(container.end(), *(pelem++)); } diff --git a/contrib/epee/include/storages/parserse_base_utils.h b/contrib/epee/include/storages/parserse_base_utils.h index c809392f4..8c6c1a64d 100644 --- a/contrib/epee/include/storages/parserse_base_utils.h +++ b/contrib/epee/include/storages/parserse_base_utils.h @@ -28,6 +28,8 @@ #pragma once +#include + namespace epee { namespace misc_utils @@ -36,8 +38,12 @@ namespace misc_utils { inline std::string transform_to_escape_sequence(const std::string& src) { - //std::stringstream res; + static const char escaped[] = "\b\f\n\r\t\v\"\\/"; + if (std::find_first_of(src.begin(), src.end(), escaped, escaped + sizeof(escaped)) == src.end()) + return src; + std::string res; + res.reserve(2 * src.size()); for(std::string::const_iterator it = src.begin(); it!=src.end(); ++it) { switch(*it) @@ -84,6 +90,7 @@ namespace misc_utils inline void match_string2(std::string::const_iterator& star_end_string, std::string::const_iterator buf_end, std::string& val) { val.clear(); + val.reserve(std::distance(star_end_string, buf_end)); bool escape_mode = false; std::string::const_iterator it = star_end_string; ++it; diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat index 200a572dd..5bcd09278 100644 Binary files a/src/blocks/checkpoints.dat and b/src/blocks/checkpoints.dat differ diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 16fa39384..8cf237adb 100755 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -185,7 +185,29 @@ namespace cryptonote ADD_CHECKPOINT(20000, "52cc7edcb49eb02f28a653b824089a726f4050eb210263ee6f4180d388a1e5cc"); ADD_CHECKPOINT(30000, "d22fde5dd240ade16d3250eb0aa5d1c16dc7cb51c20484e05eb274911032b3fa"); ADD_CHECKPOINT(40000, "aee0d642322542ba069cb1c58ab2acd3560f108d4682c3dc3cb15a54d442d91f"); - ADD_CHECKPOINT(50000, "5286ac2a0f39b3aefcba363cd71f2760bd1e0d763cbc81026ebdc3f80a86541f"); + ADD_CHECKPOINT(50000, "5286ac2a0f39b3aefcba363cd71f2760bd1e0d763cbc81026ebdc3f80a86541f"); + ADD_CHECKPOINT(53666, "3f43f56f66ef0c43cf2fd14d0d28fa2aae0ef8f40716773511345750770f1255"); //Hard fork to v9 + ADD_CHECKPOINT(54500, "8ed3078b389c2b44add007803d741b58d3fbed2e1ba4139bda702152d8773c9b"); + ADD_CHECKPOINT(55000, "4b662ceccefc3247edb4d654dd610b8fb496e85b88a5de43cc2bdd28171b15ff"); + ADD_CHECKPOINT(57000, "08a79f09f12bb5d230b63963356a760d51618e526cfc636047a6f3798217c177"); + ADD_CHECKPOINT(59000, "180b51ee2c5fbcd4362eb7a29df9422481310dd77d10bccdf8930724c31e007e"); + ADD_CHECKPOINT(59900, "18cc0653ef39cb304c68045dba5eb6b885f936281cd939dea04d0e6c9cd4ae2e"); + ADD_CHECKPOINT(60000, "0f02aa57a63f79f63dafed9063abe228a37cb19f00430dc3168b8a8f4ae8016c"); + ADD_CHECKPOINT(61000, "509aca8c54eb5fe44623768757b6e890ae39d512478c75f614cbff3d91809350"); + ADD_CHECKPOINT(62000, "7fe91ad256c08dbd961e04738968be22fb481093fbfa7959bde7796ccceba0e2"); + ADD_CHECKPOINT(62150, "1a7c75f8ebeda0e20eb5877181eafd7db0fc887e3fed43e0b27ab2e7bccafd10"); + ADD_CHECKPOINT(62269, "4969555d60742afb93925fd96d83ac28f45e6e3c0e583c9fb3c92d9b2100d38f"); + ADD_CHECKPOINT(62405, "4d0ae890cf9f875f231c7069508ad28dc429d14814b52db114dfab7519a27584"); + ADD_CHECKPOINT(62419, "bd8bf5ac4c4fb07ab4d0d492bd1699def5c095ab6943ad3b63a89d1d8b1ce748"); + ADD_CHECKPOINT(62425, "41a922dba6f3906871b2ccaf31ec9c91033470c503959093dae796deda8940ea"); + ADD_CHECKPOINT(62479, "a2e8ff4205ba2980eb70921b0b21b5fc656ee273664ea94b860c68ca069b60dd"); + ADD_CHECKPOINT(62503, "25fa115962988b4b8f8cfd22744a3e653b22ead8c8468e64caf334fc75a97d08"); + ADD_CHECKPOINT(62550, "bde522a8a81c392c98c979434aa1dd9d20b4ca52230ba6ae0362872757808a48"); + ADD_CHECKPOINT(62629, "8368e1ce1d421f1fc969364558433e2b2363d0ffcb5f2d946633095e3e6734f5"); + ADD_CHECKPOINT(62720, "f871cddd75951e2fe24c282d2bd28396fc922ea519b354ace992a0162cb333ff"); + ADD_CHECKPOINT(62733, "8331dbeeaf23173d2235a062373a437befadb6492cceb7640127bf18653a9e61"); + ADD_CHECKPOINT(62877, "62d44adc05d7d4fd9d15239c5575612207beab0bcf2da49158bf89e365441ca1"); + return true; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index ca7254bf5..fce729634 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -201,15 +201,25 @@ namespace cryptonote { crypto::key_derivation recv_derivation = AUTO_VAL_INIT(recv_derivation); bool r = hwdev.generate_key_derivation(tx_public_key, ack.m_view_secret_key, recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + if (!r) + { + MWARNING("key image helper: failed to generate_key_derivation(" << tx_public_key << ", " << ack.m_view_secret_key << ")"); + memcpy(&recv_derivation, rct::identity().bytes, sizeof(recv_derivation)); + } std::vector additional_recv_derivations; for (size_t i = 0; i < additional_tx_public_keys.size(); ++i) { crypto::key_derivation additional_recv_derivation = AUTO_VAL_INIT(additional_recv_derivation); r = hwdev.generate_key_derivation(additional_tx_public_keys[i], ack.m_view_secret_key, additional_recv_derivation); - CHECK_AND_ASSERT_MES(r, false, "key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")"); - additional_recv_derivations.push_back(additional_recv_derivation); + if (!r) + { + MWARNING("key image helper: failed to generate_key_derivation(" << additional_tx_public_keys[i] << ", " << ack.m_view_secret_key << ")"); + } + else + { + additional_recv_derivations.push_back(additional_recv_derivation); + } } boost::optional subaddr_recv_info = is_out_to_acc_precomp(subaddresses, out_key, recv_derivation, additional_recv_derivations, real_output_index,hwdev); diff --git a/src/cryptonote_basic/difficulty.cpp b/src/cryptonote_basic/difficulty.cpp index 5ec281d35..c84fff7a5 100644 --- a/src/cryptonote_basic/difficulty.cpp +++ b/src/cryptonote_basic/difficulty.cpp @@ -256,13 +256,9 @@ namespace cryptonote { assert(timestamps.size() == cumulative_difficulties.size() && timestamps.size() <= static_cast(N+1) ); - if ( cryptonote::MAINNET && height <= DIFFICULTY_HEIGHT ){ + /*if ( height <= DIFFICULTY_HEIGHT ){ return static_cast(DIFFICULTY_GUESS); - } - - if ( cryptonote::TESTNET && height <= DIFFICULTY_TESTNET_HEIGHT ){ - return static_cast(DIFFICULTY_TESTNET_GUESS); - } + }*/ for ( int64_t i = 1; i <= N; i++ ) { ST = static_cast(timestamps[i]) - static_cast(timestamps[i-1]); @@ -281,14 +277,79 @@ namespace cryptonote { next_D = std::max(next_D,(prev_D*108)/100); } - if ( cryptonote::MAINNET && next_D < DIFFICULTY_MINIMUM ) { + if ( next_D < DIFFICULTY_MINIMUM ) { return static_cast(DIFFICULTY_MINIMUM); } - else if ( cryptonote::TESTNET && next_D < DIFFICULTY_TESTNET_MINIMUM ) { - return static_cast(DIFFICULTY_TESTNET_MINIMUM); - } else { return static_cast(next_D); } } + + // LWMA-4 difficulty algorithm + // Copyright (c) 2017-2018 Zawy, MIT License + // https://github.com/zawy12/difficulty-algorithms/issues/3 + difficulty_type next_difficulty_v4(std::vector timestamps, std::vector cumulative_difficulties, size_t height) { + + uint64_t T = DIFFICULTY_TARGET_V2; + uint64_t N = DIFFICULTY_WINDOW_V2; // N=45, 60, and 90 for T=600, 120, 60. + uint64_t L(0), ST(0), next_D, prev_D, avg_D, i; + + assert(timestamps.size() == cumulative_difficulties.size() && timestamps.size() <= N+1 ); + + if ( height <= DIFFICULTY_HEIGHT + 1 ) { return DIFFICULTY_GUESS; } + + // Safely convert out-of-sequence timestamps into > 0 solvetimes. + std::vectorTS(N+1); + TS[0] = timestamps[0]; + for ( i = 1; i <= N; i++) { + if ( timestamps[i] > TS[i-1] ) { TS[i] = timestamps[i]; } + else { TS[i] = TS[i-1]; } + } + + for ( i = 1; i <= N; i++) { + // Temper long solvetime drops if they were preceded by 3 or 6 fast solves. + if ( i > 4 && TS[i]-TS[i-1] > 5*T && TS[i-1] - TS[i-4] < (14*T)/10 ) { ST = 2*T; } + else if ( i > 7 && TS[i]-TS[i-1] > 5*T && TS[i-1] - TS[i-7] < 4*T ) { ST = 2*T; } + else { // Assume normal conditions, so get ST. + // LWMA drops too much from long ST, so limit drops with a 5*T limit + ST = std::min(5*T ,TS[i] - TS[i-1]); + } + L += ST * i ; + } + if (L < N*N*T/20 ) { L = N*N*T/20; } + avg_D = ( cumulative_difficulties[N] - cumulative_difficulties[0] )/ N; + + // Prevent round off error for small D and overflow for large D. + if (avg_D > 2000000*N*N*T) { + next_D = (avg_D/(200*L))*(N*(N+1)*T*97); + } + else { next_D = (avg_D*N*(N+1)*T*97)/(200*L); } + + prev_D = cumulative_difficulties[N] - cumulative_difficulties[N-1] ; + + // Apply 10% jump rule. + if ( ( TS[N] - TS[N-1] < (2*T)/10 ) || + ( TS[N] - TS[N-2] < (5*T)/10 ) || + ( TS[N] - TS[N-3] < (8*T)/10 ) ) + { + next_D = std::max( next_D, std::min( (prev_D*110)/100, (105*avg_D)/100 ) ); + } + // Make all insignificant digits zero for easy reading. + i = 1000000000; + while (i > 1) { + if ( next_D > i*100 ) { next_D = ((next_D+i/2)/i)*i; break; } + else { i /= 10; } + } + // Make least 3 digits equal avg of past 10 solvetimes. + if ( next_D > 100000 ) { + next_D = ((next_D+500)/1000)*1000 + std::min(static_cast(999), (TS[N]-TS[N-10])/10); + } + + if ( next_D < DIFFICULTY_MINIMUM ) { + return static_cast(DIFFICULTY_MINIMUM); + } + else { + return static_cast(next_D); + } + } } diff --git a/src/cryptonote_basic/difficulty.h b/src/cryptonote_basic/difficulty.h index 247896e10..550b4c164 100644 --- a/src/cryptonote_basic/difficulty.h +++ b/src/cryptonote_basic/difficulty.h @@ -55,4 +55,5 @@ namespace cryptonote difficulty_type next_difficulty(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); difficulty_type next_difficulty_v2(std::vector timestamps, std::vector cumulative_difficulties, size_t target_seconds); difficulty_type next_difficulty_v3(std::vector timestamps, std::vector cumulative_difficulties, size_t height); + difficulty_type next_difficulty_v4(std::vector timestamps, std::vector cumulative_difficulties, size_t height); } diff --git a/src/cryptonote_config.h b/src/cryptonote_config.h index 40f637d18..ebf8a020f 100755 --- a/src/cryptonote_config.h +++ b/src/cryptonote_config.h @@ -48,6 +48,7 @@ #define CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT 60*60*2 #define CRYPTONOTE_DEFAULT_TX_SPENDABLE_AGE 4 +#define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V2 11 #define BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW 60 // MONEY_SUPPLY - total number coins to be generated @@ -81,12 +82,9 @@ #define DIFFICULTY_CUT 60 // timestamps to cut after sorting #define DIFFICULTY_BLOCKS_COUNT_V2 DIFFICULTY_WINDOW_V2 + 1 // added +1 to make N=N #define DIFFICULTY_BLOCKS_COUNT DIFFICULTY_WINDOW + DIFFICULTY_LAG -#define DIFFICULTY_HEIGHT 53666 // v9 fork height -#define DIFFICULTY_GUESS 40000000 // difficulty at fork 40m -#define DIFFICULTY_MINIMUM 10000000 // minimum difficulty set to 10m -#define DIFFICULTY_TESTNET_HEIGHT 100 -#define DIFFICULTY_TESTNET_GUESS 5069 -#define DIFFICULTY_TESTNET_MINIMUM 4069 +#define DIFFICULTY_HEIGHT 63469 // v10 fork height +#define DIFFICULTY_GUESS 100000069 // difficulty at fork 100m +#define DIFFICULTY_MINIMUM 40000069 // minimum difficulty set to 40m #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V1 DIFFICULTY_TARGET_V1 * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS #define CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_SECONDS_V2 DIFFICULTY_TARGET_V2 * CRYPTONOTE_LOCKED_TX_ALLOWED_DELTA_BLOCKS diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index 08255d394..4c1078efd 100755 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -93,6 +93,7 @@ static const struct { { 7, 1, 0, 1519605000 }, { 8, 6969, 0, 1524214739 }, { 9, 53666, 0, 1538689773 }, + { 10, 63469, 0, 1541700352 }, }; static const uint64_t mainnet_hard_fork_version_1_till = ((uint64_t)(0)); @@ -838,8 +839,11 @@ difficulty_type Blockchain::get_difficulty_for_next_block() else if (version == 8) { return next_difficulty_v2(timestamps, difficulties, target); } - else { + else if (version == 9) { return next_difficulty_v3(timestamps, difficulties, height); + } + else { + return next_difficulty_v4(timestamps, difficulties, height); } } //------------------------------------------------------------------ @@ -1056,9 +1060,12 @@ difficulty_type Blockchain::get_next_difficulty_for_alternative_chain(const std: } else if (version == 8) { return next_difficulty_v2(timestamps, cumulative_difficulties, target); - } - else { + } + else if (version == 9) { return next_difficulty_v3(timestamps, cumulative_difficulties, height); + } + else { + return next_difficulty_v4(timestamps, cumulative_difficulties, height); } } @@ -1350,12 +1357,13 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m bool Blockchain::complete_timestamps_vector(uint64_t start_top_height, std::vector& timestamps) { LOG_PRINT_L3("Blockchain::" << __func__); - - if(timestamps.size() >= BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) + const uint8_t hf_version = m_hardfork->get_current_version(); + size_t blockchain_timestamp_check_window = hf_version >= 10 ? BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V2 : BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; + if(timestamps.size() >= blockchain_timestamp_check_window) return true; CRITICAL_REGION_LOCAL(m_blockchain_lock); - size_t need_elements = BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW - timestamps.size(); + size_t need_elements = blockchain_timestamp_check_window - timestamps.size(); CHECK_AND_ASSERT_MES(start_top_height < m_db->height(), false, "internal error: passed start_height not < " << " m_db->height() -- " << start_top_height << " >= " << m_db->height()); size_t stop_offset = start_top_height > need_elements ? start_top_height - need_elements : 0; while (start_top_height != stop_offset) @@ -3203,10 +3211,12 @@ bool Blockchain::check_block_timestamp(std::vector& timestamps, const { LOG_PRINT_L3("Blockchain::" << __func__); median_ts = epee::misc_utils::median(timestamps); + const uint8_t hf_version = m_hardfork->get_current_version(); + size_t blockchain_timestamp_check_window = hf_version >= 10 ? BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V2 : BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; if(b.timestamp < median_ts) { - MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW << " blocks, " << median_ts); + MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", less than median of last " << blockchain_timestamp_check_window << " blocks, " << median_ts); return false; } @@ -3224,6 +3234,8 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons { LOG_PRINT_L3("Blockchain::" << __func__); uint64_t cryptonote_block_future_time_limit = get_current_hard_fork_version() >= 8 ? CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT_V2 : CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT; + const uint8_t hf_version = m_hardfork->get_current_version(); + size_t blockchain_timestamp_check_window = hf_version >= 10 ? BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW_V2 : BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; if(b.timestamp > get_adjusted_time() + cryptonote_block_future_time_limit) { MERROR_VER("Timestamp of block with id: " << get_block_hash(b) << ", " << b.timestamp << ", bigger than adjusted time + 10 minutes"); @@ -3231,7 +3243,7 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons } // if not enough blocks, no proper median yet, return true - if(m_db->height() < BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW) + if(m_db->height() < blockchain_timestamp_check_window) { return true; } @@ -3240,7 +3252,7 @@ bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) cons auto h = m_db->height(); // need most recent 60 blocks, get index of first of those - size_t offset = h - BLOCKCHAIN_TIMESTAMP_CHECK_WINDOW; + size_t offset = h - blockchain_timestamp_check_window; for(;offset < h; ++offset) { timestamps.push_back(m_db->get_block_timestamp(offset)); @@ -4441,7 +4453,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "16cb7e839284c1910925f8d9f8a752f38ad908ae9deec7afdfbbc4a777f5ef2e"; +static const char expected_block_hashes_hash[] = "e06ddfd59a891d5e63e80795d8e0494a7c1e3b01a1faa4ed91b015ecfc38c003"; void Blockchain::load_compiled_in_block_hashes() { const bool testnet = m_nettype == TESTNET; diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index da79b5598..19a5678e3 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -1344,16 +1344,24 @@ namespace cryptonote main_message = "The daemon is running offline and will not attempt to sync to the Monero network."; else main_message = "The daemon will start synchronizing with the network. This may take a long time to complete."; - MGINFO_BLUE(ENDL << + MGINFO_GREEN(ENDL << "\n \n" - "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░\n" - "░░░░░░░░░░░░░▄█▄░░░░████▄░████▄░█░░░░░░░░▄█▄░░░░██░░░░▄▀░░▄███▄░░░░░░░░░░░░\n" - "░░░░░░░░░░░░░█▀ ▀▄░░█░░░█░█░░░█░█░░░░░░░░█▀ ▀▄░░█ █░░▄▀░░░█▀░░░▀░░░░░░░░░░░\n" - "░░░░░░░░░░░░░█░░░░░░█░░░█░█░░░█░█░░░░░░░░█░░░░░░█▄▄█ █░▀▄░██▄▄░░░░░░░░░░░░░\n" - "░░░░░░░░░░░░░█▄░░▄▀ ▀████░▀████░███▄░░░░░█▄░░▄▀ █░░█ █░░░█░█▄░░░▄▀░░░░░░░░░\n" - "░░░░░░░░░░░░░▀███▀░░░░░░░░░░░░░░░░░░▀░░░░▀███▀░░░░░█░░███░░▀███▀░░░░░░░░░░░\n" - "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░█░░░░░░░░░░░░░░░░░░░░░░░░\n" - "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░▀░░░░░░░░░░░░░░░░░░░░░░░░░" << ENDL); + " \n" + " | __/| \n" + " | @ @ WoW! \n" + " | <> _ \n" + " | _/|------____ ((| |)) \n" + " | `--' | \n" + " ____|_ ___| |___.' \n" + " /_/_____/____/_______| \n" + "########################################################\n" + "### ____ ############ _ #### ____ ######################\n" + "###| _ | __ _ _ __ | | __ | _ | ___ __ _ ___ ###\n" + "###| | | |/ _` | '_ || |/ / | | | |/ _ | / _` |/ _ | ###\n" + "###| |_| | (_| | | | | < | |_| | (_) | (_| | __/ ###\n" + "###|____/ |__,_|_| |_|_||_| |____/ |___/ __, ||___| ###\n" + "#########################################|___/##########\n" + "########################################################"<< ENDL); MGINFO_YELLOW(ENDL << "**********************************************************************" << ENDL << main_message << ENDL << ENDL diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 684a6b363..3be08b327 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1095,7 +1095,7 @@ namespace cryptonote LockedTXN lock(m_blockchain); auto sorted_it = m_txs_by_fee_and_receive_time.begin(); - while (sorted_it != m_txs_by_fee_and_receive_time.end()) + for (; sorted_it != m_txs_by_fee_and_receive_time.end(); ++sorted_it) { txpool_tx_meta_t meta; if (!m_blockchain.get_txpool_tx_meta(sorted_it->second, meta)) @@ -1109,7 +1109,6 @@ namespace cryptonote if (max_total_size < total_size + meta.blob_size) { LOG_PRINT_L2(" would exceed maximum block size"); - sorted_it++; continue; } @@ -1122,14 +1121,12 @@ namespace cryptonote if(!get_block_reward(median_size, total_size + meta.blob_size, already_generated_coins, block_reward, version)) { LOG_PRINT_L2(" would exceed maximum block size"); - sorted_it++; continue; } coinbase = block_reward + fee + meta.fee; if (coinbase < template_accept_threshold(best_coinbase)) { LOG_PRINT_L2(" would decrease coinbase to " << print_money(coinbase)); - sorted_it++; continue; } } @@ -1173,13 +1170,11 @@ namespace cryptonote if (!ready) { LOG_PRINT_L2(" not ready to go"); - sorted_it++; continue; } if (have_key_images(k_images, tx)) { LOG_PRINT_L2(" key images already seen"); - sorted_it++; continue; } @@ -1188,7 +1183,6 @@ namespace cryptonote fee += meta.fee; best_coinbase = coinbase; append_key_images(k_images, tx); - sorted_it++; LOG_PRINT_L2(" added, new block size " << total_size << "/" << max_total_size << ", coinbase " << print_money(best_coinbase)); } @@ -1284,6 +1278,7 @@ namespace cryptonote { MWARNING("Failed to parse tx from txpool, removing"); remove.push_back(txid); + return true; } if (!insert_key_images(tx, meta.kept_by_block)) { diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 6e7240b57..e970afcde 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -1167,8 +1167,20 @@ skip: + " blocks/sec), " + std::to_string(m_block_queue.get_data_size() / 1048576.f) + " MB queued"; if (ELPP->vRegistry()->allowed(el::Level::Debug, "sync-info")) timing_message += std::string(": ") + m_block_queue.get_overview(); - MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + if(m_core.get_target_blockchain_height() == 0){ + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() << timing_message); + } else { + const int completition_percent = (m_core.get_current_blockchain_height() * 100 / m_core.get_target_blockchain_height()); + if(completition_percent < 99) {//printing completion percent only if % is < of 99 cause for 99 >= this is useless + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + << " (" << completition_percent << "% " << (m_core.get_target_blockchain_height() - m_core.get_current_blockchain_height()) + << " blocks remaining)" << timing_message); + } else { + MGINFO_YELLOW(context << " Synced " << m_core.get_current_blockchain_height() << "/" << m_core.get_target_blockchain_height() + << timing_message); + } + } } } } @@ -1752,3 +1764,4 @@ skip: m_core.stop(); } } // namespace + diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index c73d98a84..8af512208 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -438,7 +438,8 @@ bool t_rpc_command_executor::show_status() { } } - tools::success_msg_writer() << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections, uptime %ud %uh %um %us") + std::stringstream str; + str << boost::format("Height: %llu/%llu (%.1f%%) on %s%s, %s, net hash %s, v%u%s, %s, %u(out)+%u(in) connections") % (unsigned long long)ires.height % (unsigned long long)net_height % get_sync_percentage(ires) @@ -451,12 +452,21 @@ bool t_rpc_command_executor::show_status() { % (hfres.state == cryptonote::HardFork::Ready ? "up to date" : hfres.state == cryptonote::HardFork::UpdateNeeded ? "update needed" : "out of date, likely forked") % (unsigned)ires.outgoing_connections_count % (unsigned)ires.incoming_connections_count - % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0) - % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0)) - % (unsigned int)floor(fmod((uptime / 60.0), 60.0)) - % (unsigned int)fmod(uptime, 60.0) ; + // restricted RPC does not disclose start time + if (ires.start_time) + { + str << boost::format(", uptime %ud %uh %um %us") + % (unsigned int)floor(uptime / 60.0 / 60.0 / 24.0) + % (unsigned int)floor(fmod((uptime / 60.0 / 60.0), 24.0)) + % (unsigned int)floor(fmod((uptime / 60.0), 60.0)) + % (unsigned int)fmod(uptime, 60.0) + ; + } + + tools::success_msg_writer() << str.str(); + return true; } @@ -554,7 +564,7 @@ bool t_rpc_command_executor::print_blockchain_info(uint64_t start_block_index, u if (!first) std::cout << std::endl; std::cout - << "height: " << header.height << ", timestamp: " << header.timestamp << ", difficulty: " << header.difficulty + << "height: " << header.height << ", timestamp: " << header.timestamp << ", size: " << header.block_size << ", transactions: " << header.num_txes << std::endl << "major version: " << (unsigned)header.major_version << ", minor version: " << (unsigned)header.minor_version << std::endl << "block id: " << header.hash << ", previous block id: " << header.prev_hash << std::endl diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index 3ec67e7e0..635caeb16 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -863,7 +863,13 @@ namespace cryptonote boost::thread::attributes attrs; attrs.set_stack_size(THREAD_STACK_SIZE); - if(!m_core.get_miner().start(info.address, static_cast(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) + cryptonote::miner &miner= m_core.get_miner(); + if (miner.is_mining()) + { + res.status = "Already mining"; + return true; + } + if(!miner.start(info.address, static_cast(req.threads_count), attrs, req.do_background_mining, req.ignore_battery)) { res.status = "Failed, mining not started"; LOG_PRINT_L0(res.status); @@ -992,6 +998,8 @@ namespace cryptonote return r; m_core.get_pool_transactions_and_spent_keys_info(res.transactions, res.spent_key_images, !request_has_rpc_origin || !m_restricted); + for (tx_info& txi : res.transactions) + txi.tx_blob = epee::string_tools::buff_to_hex_nodelimer(txi.tx_blob); res.status = CORE_RPC_STATUS_OK; return true; } diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index 26782e6d5..d2acf5182 100755 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -64,6 +64,7 @@ #include "wallet/wallet_args.h" #include "version.h" #include +#include "wallet/message_store.h" #ifdef WIN32 #include @@ -93,13 +94,8 @@ typedef cryptonote::simple_wallet sw; m_auto_refresh_enabled.store(false, std::memory_order_relaxed); \ /* stop any background refresh, and take over */ \ m_wallet->stop(); \ - m_idle_mutex.lock(); \ - while (m_auto_refresh_refreshing) \ - m_idle_cond.notify_one(); \ - m_idle_mutex.unlock(); \ -/* if (auto_refresh_run)*/ \ - /*m_auto_refresh_thread.join();*/ \ boost::unique_lock lock(m_idle_mutex); \ + m_idle_cond.notify_all(); \ epee::misc_utils::auto_scope_leave_caller scope_exit_handler = epee::misc_utils::create_scope_leave_handler([&](){ \ m_auto_refresh_enabled.store(auto_refresh_enabled, std::memory_order_relaxed); \ }) @@ -525,6 +521,18 @@ bool parse_priority(const std::string& arg, uint32_t& priority) return false; } +std::string join_priority_strings(const char *delimiter) +{ + std::string s; + for (size_t n = 0; n < allowed_priority_strings.size(); ++n) + { + if (!s.empty()) + s += delimiter; + s += allowed_priority_strings[n]; + } + return s; +} + std::string simple_wallet::get_commands_str() { std::stringstream ss; @@ -780,6 +788,8 @@ bool simple_wallet::print_fee_info(const std::vector &args/* = std: bool simple_wallet::prepare_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -813,11 +823,20 @@ bool simple_wallet::prepare_multisig(const std::vector &args) success_msg_writer() << multisig_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use make_multisig [...] with others' multisig info"); success_msg_writer() << tr("This includes the PRIVATE view key, so needs to be disclosed only to that multisig wallet's participants "); + + if (by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::key_set, multisig_info); + } + + m_command_successful = true; return true; } bool simple_wallet::make_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); if (m_wallet->key_on_device()) { fail_msg_writer() << tr("command not supported by HW wallet"); @@ -873,6 +892,11 @@ bool simple_wallet::make_multisig(const std::vector &args) success_msg_writer() << tr("Another step is needed"); success_msg_writer() << multisig_extra_info; success_msg_writer() << tr("Send this multisig info to all other participants, then use finalize_multisig [...] with others' multisig info"); + if (by_mms) + { + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::finalizing_key_set, multisig_extra_info); + } + m_command_successful = true; return true; } } @@ -891,11 +915,14 @@ bool simple_wallet::make_multisig(const std::vector &args) success_msg_writer() << std::to_string(threshold) << "/" << total << tr(" multisig address: ") << m_wallet->get_account().get_public_address_str(m_wallet->nettype()); + m_command_successful = true; return true; } bool simple_wallet::finalize_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -940,11 +967,14 @@ bool simple_wallet::finalize_multisig(const std::vector &args) return true; } + m_command_successful = true; return true; } bool simple_wallet::export_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -970,17 +1000,24 @@ bool simple_wallet::export_multisig(const std::vector &args) return true; const std::string filename = args[0]; - if (m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) + if (!by_mms && m_wallet->confirm_export_overwrite() && !check_file_overwrite(filename)) return true; try { cryptonote::blobdata ciphertext = m_wallet->export_multisig(); - bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("failed to save file ") << filename; - return true; + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), mms::message_type::multisig_sync_data, ciphertext); + } + else + { + bool r = epee::file_io_utils::save_string_to_file(filename, ciphertext); + if (!r) + { + fail_msg_writer() << tr("failed to save file ") << filename; + return true; + } } } catch (const std::exception &e) @@ -991,11 +1028,14 @@ bool simple_wallet::export_multisig(const std::vector &args) } success_msg_writer() << tr("Multisig info exported to ") << filename; + m_command_successful = true; return true; } bool simple_wallet::import_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; uint32_t threshold, total; if (m_wallet->key_on_device()) @@ -1024,15 +1064,22 @@ bool simple_wallet::import_multisig(const std::vector &args) std::vector info; for (size_t n = 0; n < args.size(); ++n) { - const std::string filename = args[n]; - std::string data; - bool r = epee::file_io_utils::load_file_to_string(filename, data); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("failed to read file ") << filename; - return true; + info.push_back(args[n]); + } + else + { + const std::string &filename = args[n]; + std::string data; + bool r = epee::file_io_utils::load_file_to_string(filename, data); + if (!r) + { + fail_msg_writer() << tr("failed to read file ") << filename; + return true; + } + info.push_back(std::move(data)); } - info.push_back(std::move(data)); } LOCK_IDLE_SCOPE(); @@ -1044,6 +1091,7 @@ bool simple_wallet::import_multisig(const std::vector &args) // Clear line "Height xxx of xxx" std::cout << "\r \r"; success_msg_writer() << tr("Multisig info imported"); + m_command_successful = true; } catch (const std::exception &e) { @@ -1076,6 +1124,8 @@ bool simple_wallet::accept_loaded_tx(const tools::wallet2::multisig_tx_set &txs) bool simple_wallet::sign_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; if (m_wallet->key_on_device()) { @@ -1104,11 +1154,48 @@ bool simple_wallet::sign_multisig(const std::vector &args) uint32_t signers = 0; try { - bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("Failed to sign multisig transaction"); - return true; + tools::wallet2::multisig_tx_set exported_txs; + std::string ciphertext; + bool r = m_wallet->load_multisig_tx(args[0], exported_txs, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (r) + { + r = m_wallet->sign_multisig_tx(exported_txs, txids); + } + if (r) + { + ciphertext = m_wallet->save_multisig_tx(exported_txs); + if (ciphertext.empty()) + { + r = false; + } + } + if (r) + { + mms::message_type message_type = mms::message_type::fully_signed_tx; + if (txids.empty()) + { + message_type = mms::message_type::partially_signed_tx; + } + get_message_store().process_wallet_created_data(get_multisig_wallet_state(), message_type, ciphertext); + filename = "MMS"; // for the messages below + m_command_successful = true; + } + else + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } + } + else + { + bool r = m_wallet->sign_multisig_tx_from_file(filename, txids, [&](const tools::wallet2::multisig_tx_set &tx){ signers = tx.m_signers.size(); return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to sign multisig transaction"); + return true; + } } } catch (const tools::error::multisig_export_needed& e) @@ -1148,6 +1235,8 @@ bool simple_wallet::sign_multisig(const std::vector &args) bool simple_wallet::submit_multisig(const std::vector &args) { + m_command_successful = false; + bool by_mms = called_by_mms(); bool ready; uint32_t threshold; if (m_wallet->key_on_device()) @@ -1179,11 +1268,23 @@ bool simple_wallet::submit_multisig(const std::vector &args) try { tools::wallet2::multisig_tx_set txs; - bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); - if (!r) + if (by_mms) { - fail_msg_writer() << tr("Failed to load multisig transaction from file"); - return true; + bool r = m_wallet->load_multisig_tx(args[0], txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from MMS"); + return true; + } + } + else + { + bool r = m_wallet->load_multisig_tx_from_file(filename, txs, [&](const tools::wallet2::multisig_tx_set &tx){ return accept_loaded_tx(tx); }); + if (!r) + { + fail_msg_writer() << tr("Failed to load multisig transaction from file"); + return true; + } } if (txs.m_signers.size() < threshold) { @@ -1199,6 +1300,7 @@ bool simple_wallet::submit_multisig(const std::vector &args) success_msg_writer(true) << tr("Transaction successfully submitted, transaction ") << get_transaction_hash(ptx.tx) << ENDL << tr("You can check its status by using the `show_transfers` command."); } + m_command_successful = true; } catch (const std::exception &e) { @@ -1289,6 +1391,816 @@ bool simple_wallet::export_raw_multisig(const std::vector &args) return true; } +// MMS --------------------------------------------------------------------------------------------------- + +// Access to the message store, or more exactly to the list of the messages that can be changed +// by the idle thread, is guarded by the same mutex-based mechanism as access to the wallet +// as a whole and thus e.g. uses the "LOCK_IDLE_SCOPE" macro. This is a little over-cautious, but +// simple and safe. Care has to be taken however where MMS methods call other simplewallet methods +// that use "LOCK_IDLE_SCOPE" as this cannot be nested! + +// Methods for commands like "export_multisig_info" usually read data from file(s) or write data +// to files. The MMS calls now those methods as well, to produce data for messages and to process data +// from messages. As it would be quite inconvenient for the MMS to write data for such methods to files +// first or get data out of result files after the call, those methods detect a call from the MMS and +// expect data as arguments instead of files and give back data by calling 'process_wallet_created_data'. +bool simple_wallet::called_by_mms() +{ + bool by_mms = m_called_by_mms; + m_called_by_mms = false; + return by_mms; +} + +bool simple_wallet::user_confirms(const std::string &question) +{ + std::string answer = input_line(question + tr(" (Y/Yes/N/No): ")); + return !std::cin.eof() && command_line::is_yes(answer); +} + +bool simple_wallet::get_number_from_arg(const std::string &arg, uint32_t &number, const uint32_t lower_bound, const uint32_t upper_bound) +{ + bool valid = false; + try + { + number = boost::lexical_cast(arg); + valid = (number >= lower_bound) && (number <= upper_bound); + } + catch(const boost::bad_lexical_cast &) + { + } + return valid; +} + +bool simple_wallet::choose_mms_processing(const std::vector &data_list, uint32_t &choice) +{ + uint32_t choices = data_list.size(); + if (choices == 1) + { + choice = 0; + return true; + } + mms::message_store& ms = m_wallet->get_message_store(); + success_msg_writer() << tr("Choose processing:"); + std::string text; + for (size_t i = 0; i < choices; ++i) + { + const mms::processing_data &data = data_list[i]; + text = std::to_string(i+1) + ": "; + switch (data.processing) + { + case mms::message_processing::sign_tx: + text += tr("Sign tx"); + break; + case mms::message_processing::send_tx: + { + mms::message m; + ms.get_message_by_id(data.message_ids[0], m); + if (m.type == mms::message_type::fully_signed_tx) + { + text += tr("Send the tx for submission to "); + } + else + { + text += tr("Send the tx for signing to "); + } + mms::coalition_member member = ms.get_member(data.receiving_member_index); + text += ms.member_to_string(member, 50); + break; + } + case mms::message_processing::submit_tx: + text += tr("Submit tx"); + break; + default: + text += tr("unknown"); + break; + } + success_msg_writer() << text; + } + + std::string line = input_line(tr("Choice: ")); + if (std::cin.eof() || line.empty()) + { + return false; + } + bool choice_ok = get_number_from_arg(line, choice, 1, choices); + if (choice_ok) + { + choice--; + } + else + { + fail_msg_writer() << tr("Wrong choice"); + } + return choice_ok; +} + +static std::string get_human_readable_timestamp(uint64_t ts); +static std::string get_human_readable_timespan(std::chrono::seconds seconds); + +void simple_wallet::list_mms_messages(const std::vector &messages) +{ + success_msg_writer() << boost::format("%4s %-4s %-30s %-21s %7s %-15s %-40s") % tr("Id") % tr("I/O") % tr("Coalition Member") + % tr("Message Type") % tr("Height") % tr("Message State") % tr("Since"); + mms::message_store& ms = m_wallet->get_message_store(); + uint64_t now = time(NULL); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + const mms::coalition_member &member = ms.get_member(m.member_index); + bool highlight = (m.state == mms::message_state::ready_to_send) || (m.state == mms::message_state::waiting); + message_writer(m.direction == mms::message_direction::out ? console_color_green : console_color_magenta, highlight) << + boost::format("%4s %-4s %-30s %-21s %7s %-15s %-40s") % + m.id % + ms.message_direction_to_string(m.direction) % + ms.member_to_string(member, 30) % + ms.message_type_to_string(m.type) % + m.wallet_height % + ms.message_state_to_string(m.state) % + (get_human_readable_timestamp(m.modified) + ", " + get_human_readable_timespan(std::chrono::seconds(now - m.modified)) + tr(" ago")); + } +} + +void simple_wallet::show_message(const mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + const mms::coalition_member &member = ms.get_member(m.member_index); + bool display_content; + switch (m.type) + { + case mms::message_type::key_set: + case mms::message_type::finalizing_key_set: + case mms::message_type::note: + display_content = true; + break; + default: + display_content = false; + } + uint64_t now = time(NULL); + success_msg_writer() << ""; + success_msg_writer() << tr("Message ") << m.id; + success_msg_writer() << tr("In/out: ") << ms.message_direction_to_string(m.direction); + success_msg_writer() << tr("Type: ") << ms.message_type_to_string(m.type); + success_msg_writer() << tr("State: ") << boost::format(tr("%s since %s, %s ago")) % + ms.message_state_to_string(m.state) % get_human_readable_timestamp(m.modified) % get_human_readable_timespan(std::chrono::seconds(now - m.modified)); + if (m.sent == 0) + { + success_msg_writer() << tr("Sent: Never"); + } + else + { + success_msg_writer() << boost::format(tr("Sent: %s, %s ago")) % + get_human_readable_timestamp(m.sent) % get_human_readable_timespan(std::chrono::seconds(now - m.sent)); + } + success_msg_writer() << tr("Member: ") << ms.member_to_string(member, 100); + success_msg_writer() << tr("Content size: ") << m.content.length() << tr(" bytes"); + success_msg_writer() << tr("Content: ") << (display_content ? m.content : tr("(binary data)")); + + if (m.type == mms::message_type::note) + { + // Showing a note and read its text is "processing" it: Set the state accordingly + // which will also delete it from Bitmessage as a side effect + // (Without this little "twist" it would never change the state, and never get deleted) + ms.set_message_processed_or_sent(m.id); + } +} + +void simple_wallet::ask_send_all_ready_messages() +{ + mms::message_store& ms = m_wallet->get_message_store(); + std::vector ready_messages; + const std::vector &messages = ms.get_all_messages(); + for (size_t i = 0; i < messages.size(); ++i) + { + const mms::message &m = messages[i]; + if (m.state == mms::message_state::ready_to_send) + { + ready_messages.push_back(m); + } + } + if (ready_messages.size() != 0) + { + list_mms_messages(ready_messages); + bool send = ms.get_auto_send(); + if (!send) + { + send = user_confirms(tr("Send these messages now?")); + } + if (send) + { + mms::multisig_wallet_state state = get_multisig_wallet_state(); + for (size_t i = 0; i < ready_messages.size(); ++i) + { + ms.send_message(state, ready_messages[i].id); + ms.set_message_processed_or_sent(ready_messages[i].id); + } + success_msg_writer() << tr("Sent."); + } + } +} + +bool simple_wallet::get_message_from_arg(const std::string &arg, mms::message &m) +{ + mms::message_store& ms = m_wallet->get_message_store(); + bool valid_id = false; + uint32_t id; + try + { + id = (uint32_t)boost::lexical_cast(arg); + valid_id = ms.get_message_by_id(id, m); + } + catch (const boost::bad_lexical_cast &) + { + } + if (!valid_id) + { + fail_msg_writer() << tr("Invalid message id"); + } + return valid_id; +} + +void simple_wallet::mms_init(const std::vector &args) +{ + // mms init / + // Example: mms init 2/3 rbrunner BM-2cUVEbbb3H6ojddYQziK3RafJ5GPcFQv7e + // For now, assume we still have the original Monero address available, before "make_multisig" + if (args.size() != 4) + { + fail_msg_writer() << tr("usage: mms init / "); + return; + } + mms::message_store& ms = m_wallet->get_message_store(); + if (ms.get_active()) + { + if (!user_confirms(tr("The MMS is already initialized. Re-initialize by deleting all member info and messages?"))) + { + return; + } + } + uint32_t threshold; + uint32_t coalition_size; + const std::string &mn = args[1]; + std::vector numbers; + boost::split(numbers, mn, boost::is_any_of("/")); + bool mn_ok = (numbers.size() == 2) + && get_number_from_arg(numbers[0], threshold, 1, 100) + && get_number_from_arg(numbers[1], coalition_size, 2, 100); + if (mn_ok) + { + mn_ok = (threshold == coalition_size) || (threshold == (coalition_size -1)); + // Fully general cases like 3/5 not yet supported + } + if (!mn_ok) + { + fail_msg_writer() << tr("Error in threshold and/or coalition size"); + return; + } + ms.init(get_multisig_wallet_state(), args[2], args[3], coalition_size, threshold); +} + +void simple_wallet::mms_info(const std::vector &args) +{ + mms::message_store& ms = m_wallet->get_message_store(); + success_msg_writer() << boost::format("The MMS is active for %s/%s multisig.") + % ms.get_threshold() % ms.get_coalition_size(); +} + +void simple_wallet::mms_member(const std::vector &args) +// mms 0:member [1: <2:label> [3: [4:]]] +{ + mms::message_store& ms = m_wallet->get_message_store(); + const std::vector &members = ms.get_all_members(); + if (args.size() == 1) + { + // Without further parameters list all defined members + success_msg_writer() << boost::format("%2s %-20s %-s") % tr("#") % tr("Label") % tr("Transport Address"); + success_msg_writer() << boost::format("%2s %-20s %-s") % "" % "" % tr("Wownero Address"); + for (size_t i = 0; i < members.size(); ++i) + { + const mms::coalition_member &member = members[i]; + std::string label = member.label.empty() ? tr("") : member.label; + std::string monero_address; + if (member.monero_address_known) + { + monero_address = get_account_address_as_str(m_wallet->nettype(), false, member.monero_address); + } + else + { + monero_address = tr(""); + } + std::string transport_address = member.transport_address.empty() ? tr("") : member.transport_address; + success_msg_writer() << boost::format("%2s %-20s %-s") % (i + 1) % label % transport_address; + success_msg_writer() << boost::format("%2s %-20s %-s") % "" % "" % monero_address; + success_msg_writer() << ""; + } + return; + } + + uint32_t index; + bool index_valid = get_number_from_arg(args[1], index, 1, ms.get_coalition_size()); + if (index_valid) + { + index--; + } + else + { + fail_msg_writer() << tr("Invalid coalition member number ") + args[1]; + return; + } + if (args.size() < 3) + { + fail_msg_writer() << tr("mms member [