diff options
Diffstat (limited to 'src')
43 files changed, 1234 insertions, 467 deletions
diff --git a/src/blockchain_db/berkeleydb/db_bdb.cpp b/src/blockchain_db/berkeleydb/db_bdb.cpp index 3a66ecb93..e1b76ec1e 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.cpp +++ b/src/blockchain_db/berkeleydb/db_bdb.cpp @@ -313,7 +313,7 @@ void BlockchainBDB::remove_block() throw1(DB_ERROR("Failed to add removal of block hash to db transaction")); } -void BlockchainBDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) +void BlockchainBDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) { LOG_PRINT_L3("BlockchainBDB::" << __func__); check_open(); @@ -655,7 +655,7 @@ bool BlockchainBDB::for_all_blocks(std::function<bool(uint64_t, const crypto::ha return ret; } -bool BlockchainBDB::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f) const +bool BlockchainBDB::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f, bool pruned) const { LOG_PRINT_L3("BlockchainBDB::" << __func__); check_open(); diff --git a/src/blockchain_db/berkeleydb/db_bdb.h b/src/blockchain_db/berkeleydb/db_bdb.h index 238a90686..cecbba28f 100644 --- a/src/blockchain_db/berkeleydb/db_bdb.h +++ b/src/blockchain_db/berkeleydb/db_bdb.h @@ -360,7 +360,7 @@ private: virtual void remove_block(); - virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash); + virtual void add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash); virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx); @@ -381,7 +381,7 @@ private: virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; virtual bool for_all_blocks(std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const; - virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const; + virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const; virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, size_t tx_idx)> f) const; // Hard fork related storage diff --git a/src/blockchain_db/blockchain_db.cpp b/src/blockchain_db/blockchain_db.cpp index 9f760dc0d..88ac34255 100644 --- a/src/blockchain_db/blockchain_db.cpp +++ b/src/blockchain_db/blockchain_db.cpp @@ -121,10 +121,10 @@ void BlockchainDB::pop_block() pop_block(blk, txs); } -void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr) +void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr, const crypto::hash* tx_prunable_hash_ptr) { bool miner_tx = false; - crypto::hash tx_hash; + crypto::hash tx_hash, tx_prunable_hash; if (!tx_hash_ptr) { // should only need to compute hash for miner transactions @@ -135,6 +135,13 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti { tx_hash = *tx_hash_ptr; } + if (tx.version >= 2) + { + if (!tx_prunable_hash_ptr) + tx_prunable_hash = get_transaction_prunable_hash(tx); + else + tx_prunable_hash = *tx_prunable_hash_ptr; + } for (const txin_v& tx_input : tx.vin) { @@ -161,7 +168,7 @@ void BlockchainDB::add_transaction(const crypto::hash& blk_hash, const transacti } } - uint64_t tx_id = add_transaction_data(blk_hash, tx, tx_hash); + uint64_t tx_id = add_transaction_data(blk_hash, tx, tx_hash, tx_prunable_hash); std::vector<uint64_t> amount_output_indices; diff --git a/src/blockchain_db/blockchain_db.h b/src/blockchain_db/blockchain_db.h index 1ed715315..19ba32340 100644 --- a/src/blockchain_db/blockchain_db.h +++ b/src/blockchain_db/blockchain_db.h @@ -398,9 +398,10 @@ private: * @param blk_hash the hash of the block containing the transaction * @param tx the transaction to be added * @param tx_hash the hash of the transaction + * @param tx_prunable_hash the hash of the prunable part of the transaction * @return the transaction ID */ - virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) = 0; + virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) = 0; /** * @brief remove data about a transaction @@ -526,8 +527,9 @@ protected: * @param blk_hash hash of the block which has the transaction * @param tx the transaction to add * @param tx_hash_ptr the hash of the transaction, if already calculated + * @param tx_prunable_hash_ptr the hash of the prunable part of the transaction, if already calculated */ - void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL); + void add_transaction(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash* tx_hash_ptr = NULL, const crypto::hash* tx_prunable_hash_ptr = NULL); mutable uint64_t time_tx_exists = 0; //!< a performance metric uint64_t time_commit1 = 0; //!< a performance metric @@ -1120,6 +1122,33 @@ public: virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0; /** + * @brief fetches the pruned transaction blob with the given hash + * + * The subclass should return the pruned transaction stored which has the given + * hash. + * + * If the transaction does not exist, the subclass should return false. + * + * @param h the hash to look for + * + * @return true iff the transaction was found + */ + virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const = 0; + + /** + * @brief fetches the prunable transaction hash + * + * The subclass should return the hash of the prunable transaction data. + * + * If the transaction hash does not exist, the subclass should return false. + * + * @param h the tx hash to look for + * + * @return true iff the transaction was found + */ + virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const = 0; + + /** * @brief fetches the total number of transactions ever * * The subclass should return a count of all the transactions from @@ -1426,10 +1455,11 @@ public: * not found. Current implementations simply return false. * * @param std::function fn the function to run + * @param bool pruned whether to only get pruned tx data, or the whole * * @return false if the function returns false for any transaction, otherwise true */ - virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const = 0; + virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const = 0; /** * @brief runs a function over all outputs stored diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp index 5c20c8de4..300fb6d2f 100644 --- a/src/blockchain_db/lmdb/db_lmdb.cpp +++ b/src/blockchain_db/lmdb/db_lmdb.cpp @@ -52,9 +52,8 @@ using epee::string_tools::pod_to_hex; using namespace crypto; -// Increase when the DB changes in a non backward compatible way, and there -// is no automatic conversion, so that a full resync is needed. -#define VERSION 1 +// Increase when the DB structure changes +#define VERSION 2 namespace { @@ -164,7 +163,9 @@ int compare_string(const MDB_val *a, const MDB_val *b) * block_heights block hash block height * block_info block ID {block metadata} * - * txs txn ID txn blob + * txs_pruned txn ID pruned txn blob + * txs_prunable txn ID prunable txn blob + * txs_prunable_hash txn ID prunable txn hash * tx_indices txn hash {txn ID, metadata} * tx_outputs txn ID [txn amount output indices] * @@ -189,6 +190,9 @@ const char* const LMDB_BLOCK_HEIGHTS = "block_heights"; const char* const LMDB_BLOCK_INFO = "block_info"; const char* const LMDB_TXS = "txs"; +const char* const LMDB_TXS_PRUNED = "txs_pruned"; +const char* const LMDB_TXS_PRUNABLE = "txs_prunable"; +const char* const LMDB_TXS_PRUNABLE_HASH = "txs_prunable_hash"; const char* const LMDB_TX_INDICES = "tx_indices"; const char* const LMDB_TX_OUTPUTS = "tx_outputs"; @@ -764,7 +768,7 @@ void BlockchainLMDB::remove_block() throw1(DB_ERROR(lmdb_error("Failed to add removal of block info to db transaction: ", result).c_str())); } -uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash) +uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash) { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); @@ -774,7 +778,9 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons int result; uint64_t tx_id = get_tx_count(); - CURSOR(txs) + CURSOR(txs_pruned) + CURSOR(txs_prunable) + CURSOR(txs_prunable_hash) CURSOR(tx_indices) MDB_val_set(val_tx_id, tx_id); @@ -800,10 +806,35 @@ uint64_t BlockchainLMDB::add_transaction_data(const crypto::hash& blk_hash, cons if (result) throw0(DB_ERROR(lmdb_error("Failed to add tx data to db transaction: ", result).c_str())); - MDB_val_copy<blobdata> blob(tx_to_blob(tx)); - result = mdb_cursor_put(m_cur_txs, &val_tx_id, &blob, MDB_APPEND); + cryptonote::blobdata blob = tx_to_blob(tx); + MDB_val_copy<blobdata> blobval(blob); + + std::stringstream ss; + binary_archive<true> ba(ss); + bool r = const_cast<cryptonote::transaction&>(tx).serialize_base(ba); + if (!r) + throw0(DB_ERROR("Failed to serialize pruned tx")); + std::string pruned = ss.str(); + MDB_val_copy<blobdata> pruned_blob(pruned); + result = mdb_cursor_put(m_cur_txs_pruned, &val_tx_id, &pruned_blob, MDB_APPEND); if (result) - throw0(DB_ERROR(lmdb_error("Failed to add tx blob to db transaction: ", result).c_str())); + throw0(DB_ERROR(lmdb_error("Failed to add pruned tx blob to db transaction: ", result).c_str())); + + if (pruned.size() > blob.size()) + throw0(DB_ERROR("pruned tx size is larger than tx size")); + cryptonote::blobdata prunable(blob.data() + pruned.size(), blob.size() - pruned.size()); + MDB_val_copy<blobdata> prunable_blob(prunable); + result = mdb_cursor_put(m_cur_txs_prunable, &val_tx_id, &prunable_blob, MDB_APPEND); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to add prunable tx blob to db transaction: ", result).c_str())); + + if (tx.version > 1) + { + MDB_val_set(val_prunable_hash, tx_prunable_hash); + result = mdb_cursor_put(m_cur_txs_prunable_hash, &val_tx_id, &val_prunable_hash, MDB_APPEND); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to add prunable tx prunable hash to db transaction: ", result).c_str())); + } return tx_id; } @@ -819,7 +850,9 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const mdb_txn_cursors *m_cursors = &m_wcursors; CURSOR(tx_indices) - CURSOR(txs) + CURSOR(txs_pruned) + CURSOR(txs_prunable) + CURSOR(txs_prunable_hash) CURSOR(tx_outputs) MDB_val_set(val_h, tx_hash); @@ -829,11 +862,26 @@ void BlockchainLMDB::remove_transaction_data(const crypto::hash& tx_hash, const txindex *tip = (txindex *)val_h.mv_data; MDB_val_set(val_tx_id, tip->data.tx_id); - if ((result = mdb_cursor_get(m_cur_txs, &val_tx_id, NULL, MDB_SET))) - throw1(DB_ERROR(lmdb_error("Failed to locate tx for removal: ", result).c_str())); - result = mdb_cursor_del(m_cur_txs, 0); + if ((result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, NULL, MDB_SET))) + throw1(DB_ERROR(lmdb_error("Failed to locate pruned tx for removal: ", result).c_str())); + result = mdb_cursor_del(m_cur_txs_pruned, 0); + if (result) + throw1(DB_ERROR(lmdb_error("Failed to add removal of pruned tx to db transaction: ", result).c_str())); + + if ((result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, NULL, MDB_SET))) + throw1(DB_ERROR(lmdb_error("Failed to locate prunable tx for removal: ", result).c_str())); + result = mdb_cursor_del(m_cur_txs_prunable, 0); if (result) - throw1(DB_ERROR(lmdb_error("Failed to add removal of tx to db transaction: ", result).c_str())); + throw1(DB_ERROR(lmdb_error("Failed to add removal of prunable tx to db transaction: ", result).c_str())); + + if (tx.version > 1) + { + if ((result = mdb_cursor_get(m_cur_txs_prunable_hash, &val_tx_id, NULL, MDB_SET))) + throw1(DB_ERROR(lmdb_error("Failed to locate prunable hash tx for removal: ", result).c_str())); + result = mdb_cursor_del(m_cur_txs_prunable_hash, 0); + if (result) + throw1(DB_ERROR(lmdb_error("Failed to add removal of prunable hash tx to db transaction: ", result).c_str())); + } remove_tx_outputs(tip->data.tx_id, tx); @@ -1199,6 +1247,9 @@ void BlockchainLMDB::open(const std::string& filename, const int db_flags) lmdb_db_open(txn, LMDB_BLOCK_HEIGHTS, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_block_heights, "Failed to open db handle for m_block_heights"); lmdb_db_open(txn, LMDB_TXS, MDB_INTEGERKEY | MDB_CREATE, m_txs, "Failed to open db handle for m_txs"); + lmdb_db_open(txn, LMDB_TXS_PRUNED, MDB_INTEGERKEY | MDB_CREATE, m_txs_pruned, "Failed to open db handle for m_txs_pruned"); + lmdb_db_open(txn, LMDB_TXS_PRUNABLE, MDB_INTEGERKEY | MDB_CREATE, m_txs_prunable, "Failed to open db handle for m_txs_prunable"); + lmdb_db_open(txn, LMDB_TXS_PRUNABLE_HASH, MDB_INTEGERKEY | MDB_CREATE, m_txs_prunable_hash, "Failed to open db handle for m_txs_prunable_hash"); lmdb_db_open(txn, LMDB_TX_INDICES, MDB_INTEGERKEY | MDB_CREATE | MDB_DUPSORT | MDB_DUPFIXED, m_tx_indices, "Failed to open db handle for m_tx_indices"); lmdb_db_open(txn, LMDB_TX_OUTPUTS, MDB_INTEGERKEY | MDB_CREATE, m_tx_outputs, "Failed to open db handle for m_tx_outputs"); @@ -1346,6 +1397,7 @@ void BlockchainLMDB::sync() void BlockchainLMDB::safesyncmode(const bool onoff) { + MINFO("switching safe mode " << (onoff ? "on" : "off")); mdb_env_set_flags(m_env, MDB_NOSYNC|MDB_MAPASYNC, !onoff); } @@ -1364,8 +1416,12 @@ void BlockchainLMDB::reset() throw0(DB_ERROR(lmdb_error("Failed to drop m_block_info: ", result).c_str())); if (auto result = mdb_drop(txn, m_block_heights, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_block_heights: ", result).c_str())); - if (auto result = mdb_drop(txn, m_txs, 0)) - throw0(DB_ERROR(lmdb_error("Failed to drop m_txs: ", result).c_str())); + if (auto result = mdb_drop(txn, m_txs_pruned, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_pruned: ", result).c_str())); + if (auto result = mdb_drop(txn, m_txs_prunable, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable: ", result).c_str())); + if (auto result = mdb_drop(txn, m_txs_prunable_hash, 0)) + throw0(DB_ERROR(lmdb_error("Failed to drop m_txs_prunable_hash: ", result).c_str())); if (auto result = mdb_drop(txn, m_tx_indices, 0)) throw0(DB_ERROR(lmdb_error("Failed to drop m_tx_indices: ", result).c_str())); if (auto result = mdb_drop(txn, m_tx_outputs, 0)) @@ -2063,7 +2119,6 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const TXN_PREFIX_RDONLY(); RCURSOR(tx_indices); - RCURSOR(txs); MDB_val_set(key, h); bool tx_found = false; @@ -2075,8 +2130,6 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const else if (get_result != MDB_NOTFOUND) throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction index from hash ") + epee::string_tools::pod_to_hex(h) + ": ", get_result).c_str())); - // This isn't needed as part of the check. we're not checking consistency of db. - // get_result = mdb_cursor_get(m_cur_txs, &val_tx_index, &result, MDB_SET); TIME_MEASURE_FINISH(time1); time_tx_exists += time1; @@ -2088,11 +2141,6 @@ bool BlockchainLMDB::tx_exists(const crypto::hash& h) const return false; } - // Below not needed due to above comment. - // if (get_result == MDB_NOTFOUND) - // throw0(DB_ERROR(std::string("transaction with hash ").append(epee::string_tools::pod_to_hex(h)).append(" not found at index").c_str())); - // else if (get_result) - // throw0(DB_ERROR(lmdb_error(std::string("DB error attempting to fetch transaction ") + epee::string_tools::pod_to_hex(h) + " at index: ", get_result).c_str())); return true; } @@ -2158,7 +2206,43 @@ bool BlockchainLMDB::get_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd TXN_PREFIX_RDONLY(); RCURSOR(tx_indices); - RCURSOR(txs); + RCURSOR(txs_pruned); + RCURSOR(txs_prunable); + + MDB_val_set(v, h); + MDB_val result0, result1; + auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + if (get_result == 0) + { + txindex *tip = (txindex *)v.mv_data; + MDB_val_set(val_tx_id, tip->data.tx_id); + get_result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &result0, MDB_SET); + if (get_result == 0) + { + get_result = mdb_cursor_get(m_cur_txs_prunable, &val_tx_id, &result1, MDB_SET); + } + } + if (get_result == MDB_NOTFOUND) + return false; + else if (get_result) + throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx from hash", get_result).c_str())); + + bd.assign(reinterpret_cast<char*>(result0.mv_data), result0.mv_size); + bd.append(reinterpret_cast<char*>(result1.mv_data), result1.mv_size); + + TXN_POSTFIX_RDONLY(); + + return true; +} + +bool BlockchainLMDB::get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(tx_indices); + RCURSOR(txs_pruned); MDB_val_set(v, h); MDB_val result; @@ -2167,7 +2251,7 @@ bool BlockchainLMDB::get_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd { txindex *tip = (txindex *)v.mv_data; MDB_val_set(val_tx_id, tip->data.tx_id); - get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET); + get_result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &result, MDB_SET); } if (get_result == MDB_NOTFOUND) return false; @@ -2181,6 +2265,36 @@ bool BlockchainLMDB::get_tx_blob(const crypto::hash& h, cryptonote::blobdata &bd return true; } +bool BlockchainLMDB::get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + check_open(); + + TXN_PREFIX_RDONLY(); + RCURSOR(tx_indices); + RCURSOR(txs_prunable_hash); + + MDB_val_set(v, tx_hash); + MDB_val result, val_tx_prunable_hash; + auto get_result = mdb_cursor_get(m_cur_tx_indices, (MDB_val *)&zerokval, &v, MDB_GET_BOTH); + if (get_result == 0) + { + txindex *tip = (txindex *)v.mv_data; + MDB_val_set(val_tx_id, tip->data.tx_id); + get_result = mdb_cursor_get(m_cur_txs_prunable_hash, &val_tx_id, &result, MDB_SET); + } + if (get_result == MDB_NOTFOUND) + return false; + else if (get_result) + throw0(DB_ERROR(lmdb_error("DB error attempting to fetch tx prunable hash from tx hash", get_result).c_str())); + + prunable_hash = *(const crypto::hash*)result.mv_data; + + TXN_POSTFIX_RDONLY(); + + return true; +} + uint64_t BlockchainLMDB::get_tx_count() const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); @@ -2190,8 +2304,8 @@ uint64_t BlockchainLMDB::get_tx_count() const int result; MDB_stat db_stats; - if ((result = mdb_stat(m_txn, m_txs, &db_stats))) - throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str())); + if ((result = mdb_stat(m_txn, m_txs_pruned, &db_stats))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs_pruned: ", result).c_str())); TXN_POSTFIX_RDONLY(); @@ -2267,7 +2381,6 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t &global_index) const TXN_PREFIX_RDONLY(); RCURSOR(output_txs); RCURSOR(tx_indices); - RCURSOR(txs); output_data_t od; MDB_val_set(v, global_index); @@ -2287,7 +2400,7 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t &global_index) const txindex *tip = (txindex *)val_h.mv_data; MDB_val_set(val_tx_id, tip->data.tx_id); MDB_val result; - get_result = mdb_cursor_get(m_cur_txs, &val_tx_id, &result, MDB_SET); + get_result = mdb_cursor_get(m_cur_txs_pruned, &val_tx_id, &result, MDB_SET); if (get_result == MDB_NOTFOUND) throw1(TX_DNE(std::string("tx with hash ").append(epee::string_tools::pod_to_hex(ot->tx_hash)).append(" not found in db").c_str())); else if (get_result) @@ -2297,7 +2410,7 @@ output_data_t BlockchainLMDB::get_output_key(const uint64_t &global_index) const bd.assign(reinterpret_cast<char*>(result.mv_data), result.mv_size); transaction tx; - if (!parse_and_validate_tx_from_blob(bd, tx)) + if (!parse_and_validate_tx_base_from_blob(bd, tx)) throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); const tx_out tx_output = tx.vout[ot->local_index]; @@ -2516,13 +2629,14 @@ bool BlockchainLMDB::for_blocks_range(const uint64_t& h1, const uint64_t& h2, st return fret; } -bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f) const +bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f, bool pruned) const { LOG_PRINT_L3("BlockchainLMDB::" << __func__); check_open(); TXN_PREFIX_RDONLY(); - RCURSOR(txs); + RCURSOR(txs_pruned); + RCURSOR(txs_prunable); RCURSOR(tx_indices); MDB_val k; @@ -2543,16 +2657,29 @@ bool BlockchainLMDB::for_all_transactions(std::function<bool(const crypto::hash& const crypto::hash hash = ti->key; k.mv_data = (void *)&ti->data.tx_id; k.mv_size = sizeof(ti->data.tx_id); - ret = mdb_cursor_get(m_cur_txs, &k, &v, MDB_SET); + + ret = mdb_cursor_get(m_cur_txs_pruned, &k, &v, MDB_SET); if (ret == MDB_NOTFOUND) break; if (ret) throw0(DB_ERROR(lmdb_error("Failed to enumerate transactions: ", ret).c_str())); + transaction tx; blobdata bd; bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size); - transaction tx; - if (!parse_and_validate_tx_from_blob(bd, tx)) - throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); + if (pruned) + { + if (!parse_and_validate_tx_base_from_blob(bd, tx)) + throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); + } + else + { + ret = mdb_cursor_get(m_cur_txs_prunable, &k, &v, MDB_SET); + if (ret) + throw0(DB_ERROR(lmdb_error("Failed to get prunable tx data the db: ", ret).c_str())); + bd.append(reinterpret_cast<char*>(v.mv_data), v.mv_size); + if (!parse_and_validate_tx_from_blob(bd, tx)) + throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); + } if (!f(hash, tx)) { fret = false; break; @@ -3311,7 +3438,7 @@ void BlockchainLMDB::fixup() ptr = (char *)k.mv_data; \ ptr[sizeof(name)-2] = 's' -#define LOGIF(y) if (ELPP->vRegistry()->allowed(y, MONERO_DEFAULT_LOG_CATEGORY)) +#define LOGIF(y) if (ELPP->vRegistry()->allowed(y, "global")) void BlockchainLMDB::migrate_0_1() { @@ -3322,7 +3449,7 @@ void BlockchainLMDB::migrate_0_1() MDB_val k, v; char *ptr; - MLOG_YELLOW(el::Level::Info, "Migrating blockchain from DB version 0 to 1 - this may take a while:"); + MGINFO_YELLOW("Migrating blockchain from DB version 0 to 1 - this may take a while:"); MINFO("updating blocks, hf_versions, outputs, txs, and spent_keys tables..."); do { @@ -3847,11 +3974,155 @@ void BlockchainLMDB::migrate_0_1() txn.commit(); } +void BlockchainLMDB::migrate_1_2() +{ + LOG_PRINT_L3("BlockchainLMDB::" << __func__); + uint64_t i, z; + int result; + mdb_txn_safe txn(false); + MDB_val k, v; + char *ptr; + + MGINFO_YELLOW("Migrating blockchain from DB version 1 to 2 - this may take a while:"); + MINFO("updating txs_pruned and txs_prunable tables..."); + + do { + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + + MDB_stat db_stats_txs; + MDB_stat db_stats_txs_pruned; + MDB_stat db_stats_txs_prunable; + MDB_stat db_stats_txs_prunable_hash; + if ((result = mdb_stat(txn, m_txs, &db_stats_txs))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str())); + if ((result = mdb_stat(txn, m_txs_pruned, &db_stats_txs_pruned))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs_pruned: ", result).c_str())); + if ((result = mdb_stat(txn, m_txs_prunable, &db_stats_txs_prunable))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs_prunable: ", result).c_str())); + if ((result = mdb_stat(txn, m_txs_prunable_hash, &db_stats_txs_prunable_hash))) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs_prunable_hash: ", result).c_str())); + if (db_stats_txs_pruned.ms_entries != db_stats_txs_prunable.ms_entries) + throw0(DB_ERROR("Mismatched sizes for txs_pruned and txs_prunable")); + if (db_stats_txs_pruned.ms_entries == db_stats_txs.ms_entries) + { + txn.commit(); + MINFO("txs already migrated"); + break; + } + + MINFO("updating txs tables:"); + + MDB_cursor *c_old, *c_cur0, *c_cur1, *c_cur2; + i = 0; + + while(1) { + if (!(i % 1000)) { + if (i) { + result = mdb_stat(txn, m_txs, &db_stats_txs); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to query m_txs: ", result).c_str())); + LOGIF(el::Level::Info) { + std::cout << i << " / " << (i + db_stats_txs.ms_entries) << " \r" << std::flush; + } + txn.commit(); + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + } + result = mdb_cursor_open(txn, m_txs_pruned, &c_cur0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_pruned: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable, &c_cur1); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs_prunable_hash, &c_cur2); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs_prunable_hash: ", result).c_str())); + result = mdb_cursor_open(txn, m_txs, &c_old); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to open a cursor for txs: ", result).c_str())); + if (!i) { + i = db_stats_txs_pruned.ms_entries; + } + } + MDB_val_set(k, i); + result = mdb_cursor_get(c_old, &k, &v, MDB_SET); + if (result == MDB_NOTFOUND) { + txn.commit(); + break; + } + else if (result) + throw0(DB_ERROR(lmdb_error("Failed to get a record from txs: ", result).c_str())); + + cryptonote::blobdata bd; + bd.assign(reinterpret_cast<char*>(v.mv_data), v.mv_size); + transaction tx; + if (!parse_and_validate_tx_from_blob(bd, tx)) + throw0(DB_ERROR("Failed to parse tx from blob retrieved from the db")); + std::stringstream ss; + binary_archive<true> ba(ss); + bool r = tx.serialize_base(ba); + if (!r) + throw0(DB_ERROR("Failed to serialize pruned tx")); + std::string pruned = ss.str(); + + if (pruned.size() > bd.size()) + throw0(DB_ERROR("Pruned tx is larger than raw tx")); + if (memcmp(pruned.data(), bd.data(), pruned.size())) + throw0(DB_ERROR("Pruned tx is not a prefix of the raw tx")); + + MDB_val nv; + nv.mv_data = (void*)pruned.data(); + nv.mv_size = pruned.size(); + result = mdb_cursor_put(c_cur0, (MDB_val *)&k, &nv, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to put a record into txs_pruned: ", result).c_str())); + + nv.mv_data = (void*)(bd.data() + pruned.size()); + nv.mv_size = bd.size() - pruned.size(); + result = mdb_cursor_put(c_cur1, (MDB_val *)&k, &nv, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to put a record into txs_prunable: ", result).c_str())); + + if (tx.version > 1) + { + crypto::hash prunable_hash = get_transaction_prunable_hash(tx); + MDB_val_set(val_prunable_hash, prunable_hash); + result = mdb_cursor_put(c_cur2, (MDB_val *)&k, &val_prunable_hash, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to put a record into txs_prunable_hash: ", result).c_str())); + } + + result = mdb_cursor_del(c_old, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to delete a record from txs: ", result).c_str())); + + i++; + } + } while(0); + + uint32_t version = 2; + v.mv_data = (void *)&version; + v.mv_size = sizeof(version); + MDB_val_copy<const char *> vk("version"); + result = mdb_txn_begin(m_env, NULL, 0, txn); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to create a transaction for the db: ", result).c_str())); + result = mdb_put(txn, m_properties, &vk, &v, 0); + if (result) + throw0(DB_ERROR(lmdb_error("Failed to update version for the db: ", result).c_str())); + txn.commit(); +} + void BlockchainLMDB::migrate(const uint32_t oldversion) { switch(oldversion) { case 0: migrate_0_1(); /* FALLTHRU */ + case 1: + migrate_1_2(); /* FALLTHRU */ default: ; } diff --git a/src/blockchain_db/lmdb/db_lmdb.h b/src/blockchain_db/lmdb/db_lmdb.h index f1773bac8..cc1b06ca0 100644 --- a/src/blockchain_db/lmdb/db_lmdb.h +++ b/src/blockchain_db/lmdb/db_lmdb.h @@ -50,6 +50,9 @@ typedef struct mdb_txn_cursors MDB_cursor *m_txc_output_amounts; MDB_cursor *m_txc_txs; + MDB_cursor *m_txc_txs_pruned; + MDB_cursor *m_txc_txs_prunable; + MDB_cursor *m_txc_txs_prunable_hash; MDB_cursor *m_txc_tx_indices; MDB_cursor *m_txc_tx_outputs; @@ -67,6 +70,9 @@ typedef struct mdb_txn_cursors #define m_cur_output_txs m_cursors->m_txc_output_txs #define m_cur_output_amounts m_cursors->m_txc_output_amounts #define m_cur_txs m_cursors->m_txc_txs +#define m_cur_txs_pruned m_cursors->m_txc_txs_pruned +#define m_cur_txs_prunable m_cursors->m_txc_txs_prunable +#define m_cur_txs_prunable_hash m_cursors->m_txc_txs_prunable_hash #define m_cur_tx_indices m_cursors->m_txc_tx_indices #define m_cur_tx_outputs m_cursors->m_txc_tx_outputs #define m_cur_spent_keys m_cursors->m_txc_spent_keys @@ -83,6 +89,9 @@ typedef struct mdb_rflags bool m_rf_output_txs; bool m_rf_output_amounts; bool m_rf_txs; + bool m_rf_txs_pruned; + bool m_rf_txs_prunable; + bool m_rf_txs_prunable_hash; bool m_rf_tx_indices; bool m_rf_tx_outputs; bool m_rf_spent_keys; @@ -218,6 +227,8 @@ public: virtual uint64_t get_tx_unlock_time(const crypto::hash& h) const; virtual bool get_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; + virtual bool get_pruned_tx_blob(const crypto::hash& h, cryptonote::blobdata &tx) const; + virtual bool get_prunable_tx_hash(const crypto::hash& tx_hash, crypto::hash &prunable_hash) const; virtual uint64_t get_tx_count() const; @@ -254,7 +265,7 @@ public: virtual bool for_all_key_images(std::function<bool(const crypto::key_image&)>) const; virtual bool for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::function<bool(uint64_t, const crypto::hash&, const cryptonote::block&)>) const; - virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const; + virtual bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const; virtual bool for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const; virtual bool for_all_outputs(uint64_t amount, const std::function<bool(uint64_t height)> &f) const; @@ -311,7 +322,7 @@ private: virtual void remove_block(); - virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash); + virtual uint64_t add_transaction_data(const crypto::hash& blk_hash, const transaction& tx, const crypto::hash& tx_hash, const crypto::hash& tx_prunable_hash); virtual void remove_transaction_data(const crypto::hash& tx_hash, const transaction& tx); @@ -373,6 +384,9 @@ private: // migrate from DB version 0 to 1 void migrate_0_1(); + // migrate from DB version 1 to 2 + void migrate_1_2(); + void cleanup_batch(); private: @@ -383,6 +397,9 @@ private: MDB_dbi m_block_info; MDB_dbi m_txs; + MDB_dbi m_txs_pruned; + MDB_dbi m_txs_prunable; + MDB_dbi m_txs_prunable_hash; MDB_dbi m_tx_indices; MDB_dbi m_tx_outputs; diff --git a/src/blockchain_utilities/blockchain_usage.cpp b/src/blockchain_utilities/blockchain_usage.cpp index 7c3c83167..38a0b2648 100644 --- a/src/blockchain_utilities/blockchain_usage.cpp +++ b/src/blockchain_utilities/blockchain_usage.cpp @@ -234,7 +234,7 @@ int main(int argc, char* argv[]) } } return true; - }); + }, true); std::unordered_map<uint64_t, uint64_t> counts; size_t total = 0; @@ -243,10 +243,17 @@ int main(int argc, char* argv[]) counts[out.second.size()]++; total++; } - for (const auto &c: counts) + if (total > 0) + { + for (const auto &c: counts) + { + float percent = 100.f * c.second / total; + MINFO(std::to_string(c.second) << " outputs used " << c.first << " times (" << percent << "%)"); + } + } + else { - float percent = 100.f * c.second / total; - MINFO(std::to_string(c.second) << " outputs used " << c.first << " times (" << percent << "%)"); + MINFO("No outputs to process"); } LOG_PRINT_L0("Blockchain usage exported OK"); diff --git a/src/blocks/checkpoints.dat b/src/blocks/checkpoints.dat Binary files differindex cff103804..501a55673 100644 --- a/src/blocks/checkpoints.dat +++ b/src/blocks/checkpoints.dat diff --git a/src/checkpoints/checkpoints.cpp b/src/checkpoints/checkpoints.cpp index 2c11af4b2..ef1ee171d 100644 --- a/src/checkpoints/checkpoints.cpp +++ b/src/checkpoints/checkpoints.cpp @@ -207,7 +207,7 @@ namespace cryptonote ADD_CHECKPOINT(1390000, "a8f5649dd4ded60eedab475f2bec8c934681c07e3cf640e9be0617554f13ff6c"); ADD_CHECKPOINT(1450000, "ac94e8860093bc7c83e4e91215cba1d663421ecf4067a0ae609c3a8b52bcfac2"); ADD_CHECKPOINT(1530000, "01759bce497ec38e63c78b1038892169203bb78f87e488172f6b854fcd63ba7e"); - + ADD_CHECKPOINT(1579000, "7d0d7a2346373afd41ed1e744a939fc5d474a7dbaa257be5c6fff4009e789241"); return true; } diff --git a/src/crypto/crypto.cpp b/src/crypto/crypto.cpp index ba0149240..f4ef751d3 100644 --- a/src/crypto/crypto.cpp +++ b/src/crypto/crypto.cpp @@ -70,8 +70,6 @@ namespace crypto { #include "random.h" } - boost::mutex random_lock; - static inline unsigned char *operator &(ec_point &point) { return &reinterpret_cast<unsigned char &>(point); } @@ -88,6 +86,13 @@ namespace crypto { return &reinterpret_cast<const unsigned char &>(scalar); } + void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes) + { + static boost::mutex random_lock; + boost::lock_guard<boost::mutex> lock(random_lock); + generate_random_bytes_not_thread_safe(N, bytes); + } + /* generate a random 32-byte (256-bit) integer and copy it to res */ static inline void random_scalar_not_thread_safe(ec_scalar &res) { unsigned char tmp[64]; @@ -96,8 +101,10 @@ namespace crypto { memcpy(&res, tmp, 32); } static inline void random_scalar(ec_scalar &res) { - boost::lock_guard<boost::mutex> lock(random_lock); - random_scalar_not_thread_safe(res); + unsigned char tmp[64]; + generate_random_bytes_thread_safe(64, tmp); + sc_reduce(tmp); + memcpy(&res, tmp, 32); } void hash_to_scalar(const void *data, size_t length, ec_scalar &res) { diff --git a/src/crypto/crypto.h b/src/crypto/crypto.h index 81ebfb9e2..9ea0f2ec0 100644 --- a/src/crypto/crypto.h +++ b/src/crypto/crypto.h @@ -53,8 +53,6 @@ namespace crypto { #include "random.h" } - extern boost::mutex random_lock; - #pragma pack(push, 1) POD_CLASS ec_point { char data[32]; @@ -149,11 +147,12 @@ namespace crypto { const public_key *const *, std::size_t, const signature *); }; + void generate_random_bytes_thread_safe(size_t N, uint8_t *bytes); + /* Generate N random bytes */ inline void rand(size_t N, uint8_t *bytes) { - boost::lock_guard<boost::mutex> lock(random_lock); - generate_random_bytes_not_thread_safe(N, bytes); + generate_random_bytes_thread_safe(N, bytes); } /* Generate a value filled with random bytes. @@ -161,8 +160,7 @@ namespace crypto { template<typename T> typename std::enable_if<std::is_pod<T>::value, T>::type rand() { typename std::remove_cv<T>::type res; - boost::lock_guard<boost::mutex> lock(random_lock); - generate_random_bytes_not_thread_safe(sizeof(T), &res); + generate_random_bytes_thread_safe(sizeof(T), (uint8_t*)&res); return res; } diff --git a/src/cryptonote_basic/cryptonote_format_utils.cpp b/src/cryptonote_basic/cryptonote_format_utils.cpp index 3c6885896..428be1c9c 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.cpp +++ b/src/cryptonote_basic/cryptonote_format_utils.cpp @@ -779,6 +779,61 @@ namespace cryptonote return get_transaction_hash(t, res, NULL); } //--------------------------------------------------------------- + bool calculate_transaction_prunable_hash(const transaction& t, crypto::hash& res) + { + if (t.version == 1) + return false; + transaction &tt = const_cast<transaction&>(t); + std::stringstream ss; + binary_archive<true> ba(ss); + const size_t inputs = t.vin.size(); + const size_t outputs = t.vout.size(); + const size_t mixin = t.vin.empty() ? 0 : t.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(t.vin[0]).key_offsets.size() - 1 : 0; + bool r = tt.rct_signatures.p.serialize_rctsig_prunable(ba, t.rct_signatures.type, inputs, outputs, mixin); + CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures prunable"); + cryptonote::get_blob_hash(ss.str(), res); + return true; + } + //--------------------------------------------------------------- + crypto::hash get_transaction_prunable_hash(const transaction& t) + { + crypto::hash res; + CHECK_AND_ASSERT_THROW_MES(calculate_transaction_prunable_hash(t, res), "Failed to calculate tx prunable hash"); + return res; + } + //--------------------------------------------------------------- + crypto::hash get_pruned_transaction_hash(const transaction& t, const crypto::hash &pruned_data_hash) + { + // v1 transactions hash the entire blob + CHECK_AND_ASSERT_THROW_MES(t.version > 1, "Hash for pruned v1 tx cannot be calculated"); + + // v2 transactions hash different parts together, than hash the set of those hashes + crypto::hash hashes[3]; + + // prefix + get_transaction_prefix_hash(t, hashes[0]); + + transaction &tt = const_cast<transaction&>(t); + + // base rct + { + std::stringstream ss; + binary_archive<true> ba(ss); + const size_t inputs = t.vin.size(); + const size_t outputs = t.vout.size(); + bool r = tt.rct_signatures.serialize_rctsig_base(ba, inputs, outputs); + CHECK_AND_ASSERT_THROW_MES(r, "Failed to serialize rct signatures base"); + cryptonote::get_blob_hash(ss.str(), hashes[1]); + } + + // prunable rct + hashes[2] = pruned_data_hash; + + // the tx hash is the hash of the 3 hashes + crypto::hash res = cn_fast_hash(hashes, sizeof(hashes)); + return res; + } + //--------------------------------------------------------------- bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size) { // v1 transactions hash the entire blob @@ -814,14 +869,7 @@ namespace cryptonote } else { - std::stringstream ss; - binary_archive<true> ba(ss); - const size_t inputs = t.vin.size(); - const size_t outputs = t.vout.size(); - const size_t mixin = t.vin.empty() ? 0 : t.vin[0].type() == typeid(txin_to_key) ? boost::get<txin_to_key>(t.vin[0]).key_offsets.size() - 1 : 0; - bool r = tt.rct_signatures.p.serialize_rctsig_prunable(ba, t.rct_signatures.type, inputs, outputs, mixin); - CHECK_AND_ASSERT_MES(r, false, "Failed to serialize rct signatures prunable"); - cryptonote::get_blob_hash(ss.str(), hashes[2]); + CHECK_AND_ASSERT_MES(calculate_transaction_prunable_hash(t, hashes[2]), false, "Failed to get tx prunable hash"); } // the tx hash is the hash of the 3 hashes diff --git a/src/cryptonote_basic/cryptonote_format_utils.h b/src/cryptonote_basic/cryptonote_format_utils.h index 79466e9c4..8a5296d5b 100644 --- a/src/cryptonote_basic/cryptonote_format_utils.h +++ b/src/cryptonote_basic/cryptonote_format_utils.h @@ -100,7 +100,11 @@ namespace cryptonote bool get_transaction_hash(const transaction& t, crypto::hash& res); bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t& blob_size); bool get_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); + bool calculate_transaction_prunable_hash(const transaction& t, crypto::hash& res); + crypto::hash get_transaction_prunable_hash(const transaction& t); bool calculate_transaction_hash(const transaction& t, crypto::hash& res, size_t* blob_size); + crypto::hash get_pruned_transaction_hash(const transaction& t, const crypto::hash &pruned_data_hash); + blobdata get_block_hashing_blob(const block& b); bool calculate_block_hash(const block& b, crypto::hash& res); bool get_block_hash(const block& b, crypto::hash& res); diff --git a/src/cryptonote_core/blockchain.cpp b/src/cryptonote_core/blockchain.cpp index d1ef7d6fe..54d8fac31 100644 --- a/src/cryptonote_core/blockchain.cpp +++ b/src/cryptonote_core/blockchain.cpp @@ -156,7 +156,9 @@ static const struct { //------------------------------------------------------------------ Blockchain::Blockchain(tx_memory_pool& tx_pool) : m_db(), m_tx_pool(tx_pool), m_hardfork(NULL), m_timestamps_and_difficulties_height(0), m_current_block_cumul_sz_limit(0), m_current_block_cumul_sz_median(0), - m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(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_cancel(false) + m_enforce_dns_checkpoints(false), m_max_prepare_blocks_threads(4), m_db_blocks_per_sync(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_cancel(false), + m_difficulty_for_next_block_top_hash(crypto::null_hash), + m_difficulty_for_next_block(1) { LOG_PRINT_L3("Blockchain::" << __func__); } @@ -804,7 +806,17 @@ bool Blockchain::get_block_by_hash(const crypto::hash &h, block &blk, bool *orph difficulty_type Blockchain::get_difficulty_for_next_block() { LOG_PRINT_L3("Blockchain::" << __func__); - CRITICAL_REGION_LOCAL(m_blockchain_lock); + + CRITICAL_REGION_LOCAL(m_difficulty_lock); + // we can call this without the blockchain lock, it might just give us + // something a bit out of date, but that's fine since anything which + // requires the blockchain lock will have acquired it in the first place, + // and it will be unlocked only when called from the getinfo RPC + crypto::hash top_hash = get_tail_id(); + if (top_hash == m_difficulty_for_next_block_top_hash) + return m_difficulty_for_next_block; + + CRITICAL_REGION_LOCAL1(m_blockchain_lock); std::vector<uint64_t> timestamps; std::vector<difficulty_type> difficulties; auto height = m_db->height(); @@ -847,7 +859,10 @@ difficulty_type Blockchain::get_difficulty_for_next_block() m_difficulties = difficulties; } size_t target = get_difficulty_target(); - return next_difficulty(timestamps, difficulties, target); + difficulty_type diff = next_difficulty(timestamps, difficulties, target); + m_difficulty_for_next_block_top_hash = top_hash; + m_difficulty_for_next_block = diff; + return diff; } //------------------------------------------------------------------ // This function removes blocks from the blockchain until it gets to the @@ -1195,6 +1210,12 @@ bool Blockchain::create_block_template(block& b, const account_public_address& m b.prev_id = get_tail_id(); b.timestamp = time(NULL); + uint64_t median_ts; + if (!check_block_timestamp(b, median_ts)) + { + b.timestamp = median_ts; + } + diffic = get_difficulty_for_next_block(); CHECK_AND_ASSERT_MES(diffic, false, "difficulty overhead."); @@ -2092,7 +2113,7 @@ bool Blockchain::get_blocks(const t_ids_container& block_ids, t_blocks_container //TODO: return type should be void, throw on exception // alternatively, return true only if no transactions missed template<class t_ids_container, class t_tx_container, class t_missed_container> -bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const +bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2102,7 +2123,9 @@ bool Blockchain::get_transactions_blobs(const t_ids_container& txs_ids, t_tx_con try { cryptonote::blobdata tx; - if (m_db->get_tx_blob(tx_hash, tx)) + if (pruned && m_db->get_pruned_tx_blob(tx_hash, tx)) + txs.push_back(std::move(tx)); + else if (!pruned && m_db->get_tx_blob(tx_hash, tx)) txs.push_back(std::move(tx)); else missed_txs.push_back(tx_hash); @@ -2187,7 +2210,7 @@ bool Blockchain::find_blockchain_supplement(const std::list<crypto::hash>& qbloc // find split point between ours and foreign blockchain (or start at // blockchain height <req_start_block>), and return up to max_count FULL // blocks by reference. -bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const +bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, size_t max_count) const { LOG_PRINT_L3("Blockchain::" << __func__); CRITICAL_REGION_LOCAL(m_blockchain_lock); @@ -2220,7 +2243,7 @@ bool Blockchain::find_blockchain_supplement(const uint64_t req_start_block, cons block b; CHECK_AND_ASSERT_MES(parse_and_validate_block_from_blob(blocks.back().first, b), false, "internal error, invalid block"); std::list<crypto::hash> mis; - get_transactions_blobs(b.tx_hashes, blocks.back().second, mis); + get_transactions_blobs(b.tx_hashes, blocks.back().second, mis, pruned); CHECK_AND_ASSERT_MES(!mis.size(), false, "internal error, transaction from block not found"); size += blocks.back().first.size(); for (const auto &t: blocks.back().second) @@ -2696,6 +2719,7 @@ bool Blockchain::check_tx_inputs(transaction& tx, tx_verification_context &tvc, tools::threadpool& tpool = tools::threadpool::getInstance(); tools::threadpool::waiter waiter; + const auto waiter_guard = epee::misc_utils::create_scope_leave_handler([&]() { waiter.wait(); }); int threads = tpool.get_max_concurrency(); for (const auto& txin : tx.vin) @@ -3165,10 +3189,10 @@ uint64_t Blockchain::get_adjusted_time() const } //------------------------------------------------------------------ //TODO: revisit, has changed a bit on upstream -bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const +bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); - uint64_t median_ts = epee::misc_utils::median(timestamps); + median_ts = epee::misc_utils::median(timestamps); if(b.timestamp < median_ts) { @@ -3186,7 +3210,7 @@ bool Blockchain::check_block_timestamp(std::vector<uint64_t>& timestamps, const // true if the block's timestamp is not less than the timestamp of the // median of the selected blocks // false otherwise -bool Blockchain::check_block_timestamp(const block& b) const +bool Blockchain::check_block_timestamp(const block& b, uint64_t& median_ts) const { LOG_PRINT_L3("Blockchain::" << __func__); if(b.timestamp > get_adjusted_time() + CRYPTONOTE_BLOCK_FUTURE_TIME_LIMIT) @@ -3211,7 +3235,7 @@ bool Blockchain::check_block_timestamp(const block& b) const timestamps.push_back(m_db->get_block_timestamp(offset)); } - return check_block_timestamp(timestamps, b); + return check_block_timestamp(timestamps, b, median_ts); } //------------------------------------------------------------------ void Blockchain::return_tx_to_pool(std::vector<transaction> &txs) @@ -4403,7 +4427,7 @@ void Blockchain::cancel() } #if defined(PER_BLOCK_CHECKPOINT) -static const char expected_block_hashes_hash[] = "1d3df1a177bd6f752d87c0d7b960e502605742721afb39953265f1e0f7f9b01f"; +static const char expected_block_hashes_hash[] = "59261c03b54bcb21bd463f9fe40a94f40840a12642e9a3b3bfb11b35839a5fe3"; void Blockchain::load_compiled_in_block_hashes() { const bool testnet = m_nettype == TESTNET; @@ -4514,9 +4538,9 @@ bool Blockchain::for_blocks_range(const uint64_t& h1, const uint64_t& h2, std::f return m_db->for_blocks_range(h1, h2, f); } -bool Blockchain::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f) const +bool Blockchain::for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)> f, bool pruned) const { - return m_db->for_all_transactions(f); + return m_db->for_all_transactions(f, pruned); } bool Blockchain::for_all_outputs(std::function<bool(uint64_t amount, const crypto::hash &tx_hash, uint64_t height, size_t tx_idx)> f) const @@ -4531,4 +4555,5 @@ bool Blockchain::for_all_outputs(uint64_t amount, std::function<bool(uint64_t he namespace cryptonote { template bool Blockchain::get_transactions(const std::vector<crypto::hash>&, std::list<transaction>&, std::list<crypto::hash>&) const; +template bool Blockchain::get_transactions_blobs(const std::vector<crypto::hash>&, std::list<cryptonote::blobdata>&, std::list<crypto::hash>&, bool) const; } diff --git a/src/cryptonote_core/blockchain.h b/src/cryptonote_core/blockchain.h index f58885812..769e608ca 100644 --- a/src/cryptonote_core/blockchain.h +++ b/src/cryptonote_core/blockchain.h @@ -415,11 +415,12 @@ namespace cryptonote * @param blocks return-by-reference the blocks and their transactions * @param total_height return-by-reference our current blockchain height * @param start_height return-by-reference the height of the first block returned + * @param pruned whether to return full or pruned tx blobs * @param max_count the max number of blocks to get * * @return true if a block found in common or req_start_block specified, else false */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, size_t max_count) const; /** * @brief retrieves a set of blocks and their transactions, and possibly other transactions @@ -679,11 +680,12 @@ namespace cryptonote * @param txs_ids a container of hashes for which to get the corresponding transactions * @param txs return-by-reference a container to store result transactions in * @param missed_txs return-by-reference a container to store missed transactions in + * @param pruned whether to return full or pruned blobs * * @return false if an unexpected exception occurs, else true */ template<class t_ids_container, class t_tx_container, class t_missed_container> - bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; + bool get_transactions_blobs(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs, bool pruned = false) const; template<class t_ids_container, class t_tx_container, class t_missed_container> bool get_transactions(const t_ids_container& txs_ids, t_tx_container& txs, t_missed_container& missed_txs) const; @@ -858,10 +860,11 @@ namespace cryptonote * @brief perform a check on all transactions in the blockchain * * @param std::function the check to perform, pass/fail + * @param bool pruned whether to return pruned txes only * * @return false if any transaction fails the check, otherwise true */ - bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>) const; + bool for_all_transactions(std::function<bool(const crypto::hash&, const cryptonote::transaction&)>, bool pruned) const; /** * @brief perform a check on all outputs in the blockchain @@ -1008,6 +1011,10 @@ namespace cryptonote std::vector<difficulty_type> m_difficulties; uint64_t m_timestamps_and_difficulties_height; + epee::critical_section m_difficulty_lock; + crypto::hash m_difficulty_for_next_block_top_hash; + difficulty_type m_difficulty_for_next_block; + boost::asio::io_service m_async_service; boost::thread_group m_async_pool; std::unique_ptr<boost::asio::io_service::work> m_async_work_idle; @@ -1293,10 +1300,12 @@ namespace cryptonote * false otherwise * * @param b the block to be checked + * @param median_ts return-by-reference the median of timestamps * * @return true if the block's timestamp is valid, otherwise false */ - bool check_block_timestamp(const block& b) const; + bool check_block_timestamp(const block& b, uint64_t& median_ts) const; + bool check_block_timestamp(const block& b) const { uint64_t median_ts; return check_block_timestamp(b, median_ts); } /** * @brief checks a block's timestamp @@ -1309,7 +1318,8 @@ namespace cryptonote * * @return true if the block's timestamp is valid, otherwise false */ - bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const; + bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b, uint64_t& median_ts) const; + bool check_block_timestamp(std::vector<uint64_t>& timestamps, const block& b) const { uint64_t median_ts; return check_block_timestamp(timestamps, b, median_ts); } /** * @brief get the "adjusted time" diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp index 01cd56a11..d2796deeb 100644 --- a/src/cryptonote_core/cryptonote_core.cpp +++ b/src/cryptonote_core/cryptonote_core.cpp @@ -438,6 +438,7 @@ namespace cryptonote std::vector<std::string> options; boost::trim(db_sync_mode); boost::split(options, db_sync_mode, boost::is_any_of(" :")); + const bool db_sync_mode_is_default = command_line::is_arg_defaulted(vm, cryptonote::arg_db_sync_mode); for(const auto &option : options) MDEBUG("option: " << option); @@ -458,18 +459,18 @@ namespace cryptonote { safemode = true; db_flags = DBF_SAFE; - sync_mode = db_nosync; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_nosync; } else if(options[0] == "fast") { db_flags = DBF_FAST; - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } else if(options[0] == "fastest") { db_flags = DBF_FASTEST; blocks_per_sync = 1000; // default to fastest:async:1000 - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } else db_flags = DEFAULT_FLAGS; @@ -478,9 +479,9 @@ namespace cryptonote if(options.size() >= 2 && !safemode) { if(options[1] == "sync") - sync_mode = db_sync; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_sync; else if(options[1] == "async") - sync_mode = db_async; + sync_mode = db_sync_mode_is_default ? db_defaultsync : db_async; } if(options.size() >= 3 && !safemode) @@ -1053,9 +1054,9 @@ namespace cryptonote return m_blockchain_storage.find_blockchain_supplement(qblock_ids, resp); } //----------------------------------------------------------------------------------------------- - bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const + bool core::find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, size_t max_count) const { - return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, max_count); + return m_blockchain_storage.find_blockchain_supplement(req_start_block, qblock_ids, blocks, total_height, start_height, pruned, max_count); } //----------------------------------------------------------------------------------------------- bool core::get_random_outs_for_amounts(const COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::request& req, COMMAND_RPC_GET_RANDOM_OUTPUTS_FOR_AMOUNTS::response& res) const diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h index f3b9dddc0..17b5680e5 100644 --- a/src/cryptonote_core/cryptonote_core.h +++ b/src/cryptonote_core/cryptonote_core.h @@ -517,7 +517,7 @@ namespace cryptonote * * @note see Blockchain::find_blockchain_supplement(const uint64_t, const std::list<crypto::hash>&, std::list<std::pair<cryptonote::blobdata, std::list<transaction> > >&, uint64_t&, uint64_t&, size_t) const */ - bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, size_t max_count) const; + bool find_blockchain_supplement(const uint64_t req_start_block, const std::list<crypto::hash>& qblock_ids, std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > >& blocks, uint64_t& total_height, uint64_t& start_height, bool pruned, size_t max_count) const; /** * @brief gets some stats about the daemon diff --git a/src/cryptonote_core/tx_pool.cpp b/src/cryptonote_core/tx_pool.cpp index 5dfbc1dd4..bf1fe476e 100644 --- a/src/cryptonote_core/tx_pool.cpp +++ b/src/cryptonote_core/tx_pool.cpp @@ -1068,10 +1068,6 @@ namespace cryptonote //TODO: investigate whether boolean return is appropriate bool tx_memory_pool::fill_block_template(block &bl, size_t median_size, uint64_t already_generated_coins, size_t &total_size, uint64_t &fee, uint64_t &expected_reward, uint8_t version) { - // Warning: This function takes already_generated_ - // coins as an argument and appears to do nothing - // with it. - CRITICAL_REGION_LOCAL(m_transactions_lock); CRITICAL_REGION_LOCAL1(m_blockchain); diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl index 711605597..91c6c5d5e 100644 --- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl +++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl @@ -308,7 +308,8 @@ namespace cryptonote << " [Your node is " << abs_diff << " blocks (" << ((abs_diff - diff_v2) / (24 * 60 * 60 / DIFFICULTY_TARGET_V1)) + (diff_v2 / (24 * 60 * 60 / DIFFICULTY_TARGET_V2)) << " days) " << (0 <= diff ? std::string("behind") : std::string("ahead")) << "] " << ENDL << "SYNCHRONIZATION started"); - m_core.safesyncmode(false); + if (hshd.current_height >= m_core.get_current_blockchain_height() + 5) // don't switch to unsafe mode just for a few blocks + m_core.safesyncmode(false); } LOG_PRINT_L1("Remote blockchain height: " << hshd.current_height << ", id: " << hshd.top_id); context.m_state = cryptonote_connection_context::state_synchronizing; diff --git a/src/daemon/command_parser_executor.cpp b/src/daemon/command_parser_executor.cpp index 8b51b9b85..34d9fb4c8 100644 --- a/src/daemon/command_parser_executor.cpp +++ b/src/daemon/command_parser_executor.cpp @@ -315,7 +315,7 @@ bool t_command_parser_executor::start_mining(const std::vector<std::string>& arg return true; } if(nettype != cryptonote::MAINNET) - std::cout << "Mining to a " << (nettype == cryptonote::TESTNET ? "testnet" : "stagenet") << "address, make sure this is intentional!" << std::endl; + std::cout << "Mining to a " << (nettype == cryptonote::TESTNET ? "testnet" : "stagenet") << " address, make sure this is intentional!" << std::endl; uint64_t threads_count = 1; bool do_background_mining = false; bool ignore_battery = false; diff --git a/src/daemon/rpc_command_executor.cpp b/src/daemon/rpc_command_executor.cpp index 2efb501ea..487853441 100644 --- a/src/daemon/rpc_command_executor.cpp +++ b/src/daemon/rpc_command_executor.cpp @@ -74,6 +74,7 @@ namespace { << "depth: " << boost::lexical_cast<std::string>(header.depth) << std::endl << "hash: " << header.hash << std::endl << "difficulty: " << boost::lexical_cast<std::string>(header.difficulty) << std::endl + << "POW hash: " << header.pow_hash << std::endl << "reward: " << boost::lexical_cast<std::string>(header.reward); } @@ -654,6 +655,7 @@ bool t_rpc_command_executor::print_block_by_hash(crypto::hash block_hash) { epee::json_rpc::error error_resp; req.hash = epee::string_tools::pod_to_hex(block_hash); + req.fill_pow_hash = true; std::string fail_message = "Unsuccessful"; @@ -685,6 +687,7 @@ bool t_rpc_command_executor::print_block_by_height(uint64_t height) { epee::json_rpc::error error_resp; req.height = height; + req.fill_pow_hash = true; std::string fail_message = "Unsuccessful"; diff --git a/src/gen_multisig/gen_multisig.cpp b/src/gen_multisig/gen_multisig.cpp index 943589b4a..03e0a7946 100644 --- a/src/gen_multisig/gen_multisig.cpp +++ b/src/gen_multisig/gen_multisig.cpp @@ -174,7 +174,9 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_stagenet); command_line::add_arg(desc_params, arg_create_address_file); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-gen-multisig [(--testnet|--stagenet)] [--filename-base=<filename>] [--scheme=M/N] [--threshold=M] [--participants=N]", genms::tr("This program generates a set of multisig wallets - use this simpler scheme only if all the participants trust each other"), @@ -185,6 +187,8 @@ int main(int argc, char* argv[]) ); if (!vm) return 1; + if (should_terminate) + return 0; bool testnet, stagenet; uint32_t threshold = 0, total = 0; diff --git a/src/rpc/core_rpc_server.cpp b/src/rpc/core_rpc_server.cpp index e28914d96..aa105567c 100644 --- a/src/rpc/core_rpc_server.cpp +++ b/src/rpc/core_rpc_server.cpp @@ -219,18 +219,6 @@ namespace cryptonote return ss.str(); } //------------------------------------------------------------------------------------------------------------------------------ - static cryptonote::blobdata get_pruned_tx_blob(const cryptonote::blobdata &blobdata) - { - cryptonote::transaction tx; - - if (!cryptonote::parse_and_validate_tx_from_blob(blobdata, tx)) - { - MERROR("Failed to parse and validate tx from blob"); - return cryptonote::blobdata(); - } - return get_pruned_tx_blob(tx); - } - //------------------------------------------------------------------------------------------------------------------------------ bool core_rpc_server::on_get_blocks(const COMMAND_RPC_GET_BLOCKS_FAST::request& req, COMMAND_RPC_GET_BLOCKS_FAST::response& res) { PERF_TIMER(on_get_blocks); @@ -240,7 +228,7 @@ namespace cryptonote std::list<std::pair<cryptonote::blobdata, std::list<cryptonote::blobdata> > > bs; - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, bs, res.current_height, res.start_height, req.prune, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) { res.status = "Failed"; return false; @@ -272,10 +260,7 @@ namespace cryptonote for (std::list<cryptonote::blobdata>::iterator i = bd.second.begin(); i != bd.second.end(); ++i) { unpruned_size += i->size(); - if (req.prune) - res.blocks.back().txs.push_back(get_pruned_tx_blob(std::move(*i))); - else - res.blocks.back().txs.push_back(std::move(*i)); + res.blocks.back().txs.push_back(std::move(*i)); i->clear(); i->shrink_to_fit(); pruned_size += res.blocks.back().txs.back().size(); @@ -1234,7 +1219,7 @@ namespace cryptonote return reward; } //------------------------------------------------------------------------------------------------------------------------------ - bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response) + bool core_rpc_server::fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash) { PERF_TIMER(fill_block_header_response); response.major_version = blk.major_version; @@ -1250,6 +1235,7 @@ namespace cryptonote response.reward = get_block_reward(blk); response.block_size = m_core.get_blockchain_storage().get_db().get_block_size(height); response.num_txes = blk.tx_hashes.size(); + response.pow_hash = fill_pow_hash ? string_tools::pod_to_hex(get_block_longhash(blk, height)) : ""; return true; } //------------------------------------------------------------------------------------------------------------------------------ @@ -1339,7 +1325,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get last block."; return false; } - bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header); + bool response_filled = fill_block_header_response(last_block, false, last_block_height, last_block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1380,7 +1366,7 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1429,7 +1415,7 @@ namespace cryptonote return false; } res.headers.push_back(block_header_response()); - bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.headers.back()); + bool response_filled = fill_block_header_response(blk, false, block_height, block_hash, res.headers.back(), req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1462,7 +1448,7 @@ namespace cryptonote error_resp.message = "Internal error: can't get block by height. Height = " + std::to_string(req.height) + '.'; return false; } - bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, false, req.height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -1516,7 +1502,7 @@ namespace cryptonote return false; } uint64_t block_height = boost::get<txin_gen>(blk.miner_tx.vin.front()).height; - bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header); + bool response_filled = fill_block_header_response(blk, orphan, block_height, block_hash, res.block_header, req.fill_pow_hash); if (!response_filled) { error_resp.code = CORE_RPC_ERROR_CODE_INTERNAL_ERROR; @@ -2112,6 +2098,13 @@ namespace cryptonote { res.distributions.push_back({amount, slot.start_height, slot.distribution, slot.base}); found = true; + if (req.cumulative) + { + auto &distribution = res.distributions.back().distribution; + distribution[0] += slot.base; + for (size_t n = 1; n < distribution.size(); ++n) + distribution[n] += distribution[n-1]; + } break; } } diff --git a/src/rpc/core_rpc_server.h b/src/rpc/core_rpc_server.h index 86e41e047..324f219f8 100644 --- a/src/rpc/core_rpc_server.h +++ b/src/rpc/core_rpc_server.h @@ -224,7 +224,7 @@ private: //utils uint64_t get_block_reward(const block& blk); - bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response); + bool fill_block_header_response(const block& blk, bool orphan_status, uint64_t height, const crypto::hash& hash, block_header_response& response, bool fill_pow_hash); enum invoke_http_mode { JON, BIN, JON_RPC }; template <typename COMMAND_TYPE> bool use_bootstrap_daemon_if_necessary(const invoke_http_mode &mode, const std::string &command_name, const typename COMMAND_TYPE::request& req, typename COMMAND_TYPE::response& res, bool &r); diff --git a/src/rpc/core_rpc_server_commands_defs.h b/src/rpc/core_rpc_server_commands_defs.h index 250c88e90..70e186848 100644 --- a/src/rpc/core_rpc_server_commands_defs.h +++ b/src/rpc/core_rpc_server_commands_defs.h @@ -49,7 +49,7 @@ namespace cryptonote // advance which version they will stop working with // Don't go over 32767 for any of these #define CORE_RPC_VERSION_MAJOR 1 -#define CORE_RPC_VERSION_MINOR 19 +#define CORE_RPC_VERSION_MINOR 20 #define MAKE_CORE_RPC_VERSION(major,minor) (((major)<<16)|(minor)) #define CORE_RPC_VERSION MAKE_CORE_RPC_VERSION(CORE_RPC_VERSION_MAJOR, CORE_RPC_VERSION_MINOR) @@ -1165,6 +1165,7 @@ namespace cryptonote uint64_t reward; uint64_t block_size; uint64_t num_txes; + std::string pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(major_version) @@ -1180,6 +1181,7 @@ namespace cryptonote KV_SERIALIZE(reward) KV_SERIALIZE(block_size) KV_SERIALIZE(num_txes) + KV_SERIALIZE(pow_hash) END_KV_SERIALIZE_MAP() }; @@ -1187,7 +1189,10 @@ namespace cryptonote { struct request { + bool fill_pow_hash; + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1211,9 +1216,11 @@ namespace cryptonote struct request { std::string hash; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(hash) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1237,9 +1244,11 @@ namespace cryptonote struct request { uint64_t height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1264,10 +1273,12 @@ namespace cryptonote { std::string hash; uint64_t height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(hash) KV_SERIALIZE(height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; @@ -1620,10 +1631,12 @@ namespace cryptonote { uint64_t start_height; uint64_t end_height; + bool fill_pow_hash; BEGIN_KV_SERIALIZE_MAP() KV_SERIALIZE(start_height) KV_SERIALIZE(end_height) + KV_SERIALIZE_OPT(fill_pow_hash, false); END_KV_SERIALIZE_MAP() }; diff --git a/src/rpc/daemon_handler.cpp b/src/rpc/daemon_handler.cpp index 29020aa57..39f169cdf 100644 --- a/src/rpc/daemon_handler.cpp +++ b/src/rpc/daemon_handler.cpp @@ -52,7 +52,7 @@ namespace rpc { std::list<std::pair<blobdata, std::list<blobdata> > > blocks; - if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.start_height, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) + if(!m_core.find_blockchain_supplement(req.start_height, req.block_ids, blocks, res.current_height, res.start_height, req.prune, COMMAND_RPC_GET_BLOCKS_FAST_MAX_COUNT)) { res.status = Message::STATUS_FAILED; res.error_details = "core::find_blockchain_supplement() returned false"; diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp index d573f317b..397614328 100644 --- a/src/simplewallet/simplewallet.cpp +++ b/src/simplewallet/simplewallet.cpp @@ -131,6 +131,7 @@ namespace const command_line::arg_descriptor<bool> arg_restore_multisig_wallet = {"restore-multisig-wallet", sw::tr("Recover multisig wallet using Electrum-style mnemonic seed"), false}; const command_line::arg_descriptor<bool> arg_non_deterministic = {"non-deterministic", sw::tr("Generate non-deterministic view and spend keys"), false}; const command_line::arg_descriptor<bool> arg_trusted_daemon = {"trusted-daemon", sw::tr("Enable commands which rely on a trusted daemon"), false}; + const command_line::arg_descriptor<bool> arg_untrusted_daemon = {"untrusted-daemon", sw::tr("Disable commands which rely on a trusted daemon"), false}; const command_line::arg_descriptor<bool> arg_allow_mismatched_daemon_version = {"allow-mismatched-daemon-version", sw::tr("Allow communicating with a daemon that uses a different RPC version"), false}; const command_line::arg_descriptor<uint64_t> arg_restore_height = {"restore-height", sw::tr("Restore from specific blockchain height"), 0}; const command_line::arg_descriptor<bool> arg_do_not_relay = {"do-not-relay", sw::tr("The newly created transaction will not be relayed to the monero network"), false}; @@ -1077,7 +1078,7 @@ bool simple_wallet::import_multisig(const std::vector<std::string> &args) fail_msg_writer() << tr("Failed to import multisig info: ") << e.what(); return true; } - if (m_trusted_daemon) + if (is_daemon_trusted()) { try { @@ -1229,7 +1230,7 @@ bool simple_wallet::submit_multisig(const std::vector<std::string> &args) } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -3117,18 +3118,21 @@ bool simple_wallet::init(const boost::program_options::variables_map& vm) return false; } - // set --trusted-daemon if local - try + // set --trusted-daemon if local and not overridden + if (!m_trusted_daemon) { - if (tools::is_local_address(m_wallet->get_daemon_address())) + try { - MINFO(tr("Daemon is local, assuming trusted")); - m_trusted_daemon = true; + if (tools::is_local_address(m_wallet->get_daemon_address())) + { + MINFO(tr("Daemon is local, assuming trusted")); + m_trusted_daemon = true; + } } + catch (const std::exception &e) { } } - catch (const std::exception &e) { } - if (!m_trusted_daemon) + if (!is_daemon_trusted()) message_writer() << (boost::format(tr("Warning: using an untrusted daemon at %s, privacy will be lessened")) % m_wallet->get_daemon_address()).str(); if (m_wallet->get_ring_database().empty()) @@ -3162,7 +3166,10 @@ bool simple_wallet::handle_command_line(const boost::program_options::variables_ m_restore_deterministic_wallet = command_line::get_arg(vm, arg_restore_deterministic_wallet); m_restore_multisig_wallet = command_line::get_arg(vm, arg_restore_multisig_wallet); m_non_deterministic = command_line::get_arg(vm, arg_non_deterministic); - m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon); + if (!command_line::is_arg_defaulted(vm, arg_trusted_daemon) || !command_line::is_arg_defaulted(vm, arg_untrusted_daemon)) + m_trusted_daemon = command_line::get_arg(vm, arg_trusted_daemon) && !command_line::get_arg(vm, arg_untrusted_daemon); + if (!command_line::is_arg_defaulted(vm, arg_trusted_daemon) && !command_line::is_arg_defaulted(vm, arg_untrusted_daemon)) + message_writer() << tr("--trusted-daemon and --untrusted-daemon are both seen, assuming untrusted"); m_allow_mismatched_daemon_version = command_line::get_arg(vm, arg_allow_mismatched_daemon_version); m_restore_height = command_line::get_arg(vm, arg_restore_height); m_do_not_relay = command_line::get_arg(vm, arg_do_not_relay); @@ -3489,6 +3496,17 @@ bool simple_wallet::open_wallet(const boost::program_options::variables_map& vm) fail_msg_writer() << tr("wallet file path not valid: ") << m_wallet_file; return false; } + + bool keys_file_exists; + bool wallet_file_exists; + + tools::wallet2::wallet_exists(m_wallet_file, keys_file_exists, wallet_file_exists); + if(!keys_file_exists) + { + fail_msg_writer() << tr("Key file not found. Failed to open wallet"); + return false; + } + epee::wipeable_string password; try { @@ -3649,7 +3667,7 @@ bool simple_wallet::save_watch_only(const std::vector<std::string> &args/* = std //---------------------------------------------------------------------------------------------------- bool simple_wallet::start_mining(const std::vector<std::string>& args) { - if (!m_trusted_daemon) + if (!is_daemon_trusted()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -3830,7 +3848,7 @@ void simple_wallet::on_skip_transaction(uint64_t height, const crypto::hash &txi //---------------------------------------------------------------------------------------------------- bool simple_wallet::refresh_main(uint64_t start_height, bool reset, bool is_init) { - if (!try_connect_to_daemon()) + if (!try_connect_to_daemon(is_init)) return true; LOCK_IDLE_SCOPE(); @@ -4145,7 +4163,7 @@ bool simple_wallet::show_blockchain_height(const std::vector<std::string>& args) //---------------------------------------------------------------------------------------------------- bool simple_wallet::rescan_spent(const std::vector<std::string> &args) { - if (!m_trusted_daemon) + if (!is_daemon_trusted()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -4491,16 +4509,16 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri return true; } unlock_block = bc_height + locked_blocks; - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, unlock_block /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); break; case TransferNew: - ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions_2(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); break; default: LOG_ERROR("Unknown transfer method, using original"); /* FALLTHRU */ case TransferOriginal: - ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); + ptx_vector = m_wallet->create_transactions(dsts, fake_outs_count, 0 /* unlock_time */, priority, extra, is_daemon_trusted()); break; } @@ -4676,7 +4694,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -4713,7 +4731,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(m_trusted_daemon); + auto ptx_vector = m_wallet->create_unmixable_sweep_transactions(is_daemon_trusted()); if (ptx_vector.empty()) { @@ -4784,7 +4802,7 @@ bool simple_wallet::sweep_unmixable(const std::vector<std::string> &args_) } catch (const std::exception &e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -4933,7 +4951,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, m_trusted_daemon); + auto ptx_vector = m_wallet->create_transactions_all(below, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_current_subaddress_account, subaddr_indices, is_daemon_trusted()); if (ptx_vector.empty()) { @@ -5017,7 +5035,7 @@ bool simple_wallet::sweep_main(uint64_t below, const std::vector<std::string> &a } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -5146,7 +5164,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) try { // figure out what tx will be necessary - auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, m_trusted_daemon); + auto ptx_vector = m_wallet->create_transactions_single(ki, info.address, info.is_subaddress, fake_outs_count, 0 /* unlock_time */, priority, extra, is_daemon_trusted()); if (ptx_vector.empty()) { @@ -5216,7 +5234,7 @@ bool simple_wallet::sweep_single(const std::vector<std::string> &args_) } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -5521,7 +5539,7 @@ bool simple_wallet::submit_transfer(const std::vector<std::string> &args_) } catch (const std::exception& e) { - handle_transfer_exception(std::current_exception(), m_trusted_daemon); + handle_transfer_exception(std::current_exception(), is_daemon_trusted()); } catch (...) { @@ -7109,7 +7127,7 @@ bool simple_wallet::import_key_images(const std::vector<std::string> &args) fail_msg_writer() << tr("command not supported by HW wallet"); return true; } - if (!m_trusted_daemon) + if (!is_daemon_trusted()) { fail_msg_writer() << tr("this command requires a trusted daemon. Enable with --trusted-daemon"); return true; @@ -7495,6 +7513,7 @@ int main(int argc, char* argv[]) command_line::add_arg(desc_params, arg_non_deterministic ); command_line::add_arg(desc_params, arg_electrum_seed ); command_line::add_arg(desc_params, arg_trusted_daemon); + command_line::add_arg(desc_params, arg_untrusted_daemon); command_line::add_arg(desc_params, arg_allow_mismatched_daemon_version); command_line::add_arg(desc_params, arg_restore_height); command_line::add_arg(desc_params, arg_do_not_relay); @@ -7505,7 +7524,9 @@ int main(int argc, char* argv[]) po::positional_options_description positional_options; positional_options.add(arg_command.name, -1); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-cli [--wallet-file=<file>|--generate-new-wallet=<file>] [<COMMAND>]", sw::tr("This is the command line monero wallet. It needs to connect to a monero\ndaemon to work correctly.\nWARNING: Do not reuse your Monero keys on an another fork, UNLESS this fork has key reuse mitigations built in. Doing so will harm your privacy."), @@ -7520,6 +7541,11 @@ int main(int argc, char* argv[]) return 1; } + if (should_terminate) + { + return 0; + } + cryptonote::simple_wallet w; const bool r = w.init(*vm); CHECK_AND_ASSERT_MES(r, 1, sw::tr("Failed to initialize wallet")); diff --git a/src/simplewallet/simplewallet.h b/src/simplewallet/simplewallet.h index 39a91c5f5..7a788d432 100644 --- a/src/simplewallet/simplewallet.h +++ b/src/simplewallet/simplewallet.h @@ -229,6 +229,7 @@ namespace cryptonote bool print_ring_members(const std::vector<tools::wallet2::pending_tx>& ptx_vector, std::ostream& ostr); std::string get_prompt() const; bool print_seed(bool encrypted); + bool is_daemon_trusted() const { return *m_trusted_daemon; } /*! * \brief Prints the seed with a nice message @@ -331,7 +332,7 @@ namespace cryptonote bool m_restore_deterministic_wallet; // recover flag bool m_restore_multisig_wallet; // recover flag bool m_non_deterministic; // old 2-random generation - bool m_trusted_daemon; + boost::optional<bool> m_trusted_daemon; bool m_allow_mismatched_daemon_version; bool m_restoring; // are we restoring, by whatever method? uint64_t m_restore_height; // optional diff --git a/src/version.cpp.in b/src/version.cpp.in index f83a85d9d..9fed91d99 100644 --- a/src/version.cpp.in +++ b/src/version.cpp.in @@ -1,5 +1,5 @@ #define DEF_MONERO_VERSION_TAG "@VERSIONTAG@" -#define DEF_MONERO_VERSION "0.12.0.0-master" +#define DEF_MONERO_VERSION "0.12.1.0-master" #define DEF_MONERO_RELEASE_NAME "Lithium Luna" #define DEF_MONERO_VERSION_FULL DEF_MONERO_VERSION "-" DEF_MONERO_VERSION_TAG diff --git a/src/wallet/api/pending_transaction.cpp b/src/wallet/api/pending_transaction.cpp index ff4619f0f..8d200220d 100644 --- a/src/wallet/api/pending_transaction.cpp +++ b/src/wallet/api/pending_transaction.cpp @@ -34,6 +34,7 @@ #include "cryptonote_basic/cryptonote_format_utils.h" #include "cryptonote_basic/cryptonote_basic_impl.h" +#include "common/base58.h" #include <memory> #include <vector> @@ -102,6 +103,11 @@ bool PendingTransactionImpl::commit(const std::string &filename, bool overwrite) } // Commit tx else { + auto multisigState = m_wallet.multisig(); + if (multisigState.isMultisig && m_signers.size() < multisigState.threshold) { + throw runtime_error("Not enough signers to send multisig transaction"); + } + m_wallet.pauseRefresh(); while (!m_pending_tx.empty()) { auto & ptx = m_pending_tx.back(); @@ -188,6 +194,53 @@ std::vector<std::set<uint32_t>> PendingTransactionImpl::subaddrIndices() const return result; } +std::string PendingTransactionImpl::multisigSignData() { + try { + if (!m_wallet.multisig().isMultisig) { + throw std::runtime_error("wallet is not multisig"); + } + + auto cipher = m_wallet.m_wallet->save_multisig_tx(m_pending_tx); + return epee::string_tools::buff_to_hex_nodelimer(cipher); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't multisig sign data: ")) + e.what(); + } + + return std::string(); +} + +void PendingTransactionImpl::signMultisigTx() { + try { + std::vector<crypto::hash> ignore; + + tools::wallet2::multisig_tx_set txSet; + txSet.m_ptx = m_pending_tx; + txSet.m_signers = m_signers; + + if (!m_wallet.m_wallet->sign_multisig_tx(txSet, ignore)) { + throw std::runtime_error("couldn't sign multisig transaction"); + } + + std::swap(m_pending_tx, txSet.m_ptx); + std::swap(m_signers, txSet.m_signers); + } catch (const std::exception& e) { + m_status = Status_Error; + m_errorString = std::string(tr("Couldn't sign multisig transaction: ")) + e.what(); + } +} + +std::vector<std::string> PendingTransactionImpl::signersKeys() const { + std::vector<std::string> keys; + keys.reserve(m_signers.size()); + + for (const auto& signer: m_signers) { + keys.emplace_back(tools::base58::encode(cryptonote::t_serializable_object_to_blob(signer))); + } + + return keys; +} + } namespace Bitmonero = Monero; diff --git a/src/wallet/api/pending_transaction.h b/src/wallet/api/pending_transaction.h index d0bd66eb5..4f963c134 100644 --- a/src/wallet/api/pending_transaction.h +++ b/src/wallet/api/pending_transaction.h @@ -55,6 +55,10 @@ public: std::vector<std::set<uint32_t>> subaddrIndices() const; // TODO: continue with interface; + std::string multisigSignData(); + void signMultisigTx(); + std::vector<std::string> signersKeys() const; + private: friend class WalletImpl; WalletImpl &m_wallet; @@ -62,6 +66,7 @@ private: int m_status; std::string m_errorString; std::vector<tools::wallet2::pending_tx> m_pending_tx; + std::unordered_set<crypto::public_key> m_signers; }; diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp index 367011eaa..fdecacd8f 100644 --- a/src/wallet/api/wallet.cpp +++ b/src/wallet/api/wallet.cpp @@ -69,14 +69,48 @@ namespace { // Connection timeout 30 sec static const int DEFAULT_CONNECTION_TIMEOUT_MILLIS = 1000 * 30; - std::string get_default_ringdb_path() + std::string get_default_ringdb_path(cryptonote::network_type nettype) { boost::filesystem::path dir = tools::get_default_data_dir(); // remove .bitmonero, replace with .shared-ringdb dir = dir.remove_filename(); dir /= ".shared-ringdb"; + if (nettype == cryptonote::TESTNET) + dir /= "testnet"; + else if (nettype == cryptonote::STAGENET) + dir /= "stagenet"; return dir.string(); } + + void checkMultisigWalletReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (!ready) { + throw runtime_error("Multisig wallet is not finalized yet"); + } + } + + void checkMultisigWalletNotReady(const tools::wallet2* wallet) { + if (!wallet) { + throw runtime_error("Wallet is not initialized yet"); + } + + bool ready; + if (!wallet->multisig(&ready)) { + throw runtime_error("Wallet is not multisig"); + } + + if (ready) { + throw runtime_error("Multisig wallet is already finalized"); + } + } } struct Wallet2CallbackImpl : public tools::i_wallet2_callback @@ -395,9 +429,9 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Critical; + std::string error = "attempting to generate or restore wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusCritical(error); return false; } // TODO: validate language @@ -406,11 +440,10 @@ bool WalletImpl::create(const std::string &path, const std::string &password, co try { recovery_val = m_wallet->generate(path, password, secret_key, false, false); m_password = password; - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); return false; } @@ -434,9 +467,9 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas // add logic to error out if new wallet requested but named wallet file exists if (keys_file_exists || wallet_file_exists) { - m_errorString = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; - LOG_ERROR(m_errorString); - m_status = Status_Error; + std::string error = "attempting to generate view only wallet, but specified file(s) exist. Exiting to not risk overwriting."; + LOG_ERROR(error); + setStatusError(error); return false; } // TODO: validate language @@ -472,11 +505,10 @@ bool WalletImpl::createWatchOnly(const std::string &path, const std::string &pas uint64_t spent = 0; uint64_t unspent = 0; view_wallet->import_key_images(key_images,spent,unspent,false); - m_status = Status_Ok; + clearStatus(); } catch (const std::exception &e) { LOG_ERROR("Error creating view only wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } // Store wallet @@ -503,8 +535,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::address_parse_info info; if(!get_account_address_from_str(info, m_wallet->nettype(), address_string)) { - m_errorString = tr("failed to parse address"); - m_status = Status_Error; + setStatusError(tr("failed to parse address")); return false; } @@ -515,8 +546,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, cryptonote::blobdata spendkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(spendkey_string, spendkey_data) || spendkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret spend key")); return false; } has_spendkey = true; @@ -525,15 +555,13 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, // parse view secret key if (viewkey_string.empty()) { - m_errorString = tr("No view key supplied, cancelled"); - m_status = Status_Error; + setStatusError(tr("No view key supplied, cancelled")); return false; } cryptonote::blobdata viewkey_data; if(!epee::string_tools::parse_hexstr_to_binbuff(viewkey_string, viewkey_data) || viewkey_data.size() != sizeof(crypto::secret_key)) { - m_errorString = tr("failed to parse secret view key"); - m_status = Status_Error; + setStatusError(tr("failed to parse secret view key")); return false; } crypto::secret_key viewkey = *reinterpret_cast<const crypto::secret_key*>(viewkey_data.data()); @@ -542,24 +570,20 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, crypto::public_key pkey; if(has_spendkey) { if (!crypto::secret_key_to_public_key(spendkey, pkey)) { - m_errorString = tr("failed to verify secret spend key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret spend key")); return false; } if (info.address.m_spend_public_key != pkey) { - m_errorString = tr("spend key does not match address"); - m_status = Status_Error; + setStatusError(tr("spend key does not match address")); return false; } } if (!crypto::secret_key_to_public_key(viewkey, pkey)) { - m_errorString = tr("failed to verify secret view key"); - m_status = Status_Error; + setStatusError(tr("failed to verify secret view key")); return false; } if (info.address.m_view_public_key != pkey) { - m_errorString = tr("view key does not match address"); - m_status = Status_Error; + setStatusError(tr("view key does not match address")); return false; } @@ -577,8 +601,7 @@ bool WalletImpl::recoverFromKeysWithPassword(const std::string &path, } catch (const std::exception& e) { - m_errorString = string(tr("failed to generate new wallet: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("failed to generate new wallet: ")) + e.what()); return false; } return true; @@ -599,16 +622,15 @@ bool WalletImpl::open(const std::string &path, const std::string &password) // Rebuilding wallet cache, using refresh height from .keys file m_rebuildWalletCache = true; } - m_wallet->set_ring_database(get_default_ringdb_path()); + m_wallet->set_ring_database(get_default_ringdb_path(m_wallet->nettype())); m_wallet->load(path, password); m_password = password; } catch (const std::exception &e) { LOG_ERROR("Error opening wallet: " << e.what()); - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::recover(const std::string &path, const std::string &seed) @@ -621,9 +643,8 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c clearStatus(); m_errorString.clear(); if (seed.empty()) { - m_errorString = "Electrum seed is empty"; - LOG_ERROR(m_errorString); - m_status = Status_Error; + LOG_ERROR("Electrum seed is empty"); + setStatusError(tr("Electrum seed is empty")); return false; } @@ -631,8 +652,7 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c crypto::secret_key recovery_key; std::string old_language; if (!crypto::ElectrumWords::words_to_bytes(seed, recovery_key, old_language)) { - m_errorString = "Electrum-style word list failed verification"; - m_status = Status_Error; + setStatusError(tr("Electrum-style word list failed verification")); return false; } @@ -644,10 +664,9 @@ bool WalletImpl::recover(const std::string &path, const std::string &password, c m_wallet->generate(path, password, recovery_key, true, false); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } bool WalletImpl::close(bool store) @@ -671,8 +690,7 @@ bool WalletImpl::close(bool store) result = true; clearStatus(); } catch (const std::exception &e) { - m_status = Status_Critical; - m_errorString = e.what(); + setStatusCritical(e.what()); LOG_ERROR("Error closing wallet: " << e.what()); } return result; @@ -698,14 +716,22 @@ void WalletImpl::setSeedLanguage(const std::string &arg) int WalletImpl::status() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_status; } std::string WalletImpl::errorString() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); return m_errorString; } +void WalletImpl::statusWithErrorString(int& status, std::string& errorString) const { + boost::lock_guard<boost::mutex> l(m_statusMutex); + status = m_status; + errorString = m_errorString; +} + bool WalletImpl::setPassword(const std::string &password) { clearStatus(); @@ -713,10 +739,9 @@ bool WalletImpl::setPassword(const std::string &password) m_wallet->rewrite(m_wallet->get_wallet_file(), password); m_password = password; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } - return m_status == Status_Ok; + return status() == Status_Ok; } std::string WalletImpl::address(uint32_t accountIndex, uint32_t addressIndex) const @@ -769,11 +794,11 @@ bool WalletImpl::store(const std::string &path) } } catch (const std::exception &e) { LOG_ERROR("Error saving wallet: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); + return false; } - return m_status == Status_Ok; + return true; } string WalletImpl::filename() const @@ -806,8 +831,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ { cryptonote::COMMAND_RPC_IMPORT_WALLET_REQUEST::response response; if(!m_wallet->light_wallet_import_wallet_request(response)){ - m_errorString = tr("Failed to send import wallet request"); - m_status = Status_Error; + setStatusError(tr("Failed to send import wallet request")); return false; } fee = response.import_fee; @@ -820,8 +844,7 @@ bool WalletImpl::lightWalletImportWalletRequest(std::string &payment_id, uint64_ catch (const std::exception &e) { LOG_ERROR("Error sending import wallet request: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -870,12 +893,9 @@ uint64_t WalletImpl::daemonBlockChainHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } return result; } @@ -892,12 +912,9 @@ uint64_t WalletImpl::daemonBlockChainTargetHeight() const if (!err.empty()) { LOG_ERROR(__FUNCTION__ << ": " << err); result = 0; - m_errorString = err; - m_status = Status_Error; - + setStatusError(err); } else { - m_status = Status_Ok; - m_errorString = ""; + clearStatus(); } // Target height can be 0 when daemon is synced. Use blockchain height instead. if(result == 0) @@ -921,8 +938,10 @@ bool WalletImpl::synchronized() const bool WalletImpl::refresh() { clearStatus(); + //TODO: make doRefresh return bool to know whether the error occured during refresh or not + //otherwise one may try, say, to send transaction, transfer fails and this method returns false doRefresh(); - return m_status == Status_Ok; + return status() == Status_Ok; } void WalletImpl::refreshAsync() @@ -952,8 +971,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file clearStatus(); UnsignedTransactionImpl * transaction = new UnsignedTransactionImpl(*this); if (!m_wallet->load_unsigned_tx(unsigned_filename, transaction->m_unsigned_tx_set)){ - m_errorString = tr("Failed to load unsigned transactions"); - m_status = Status_Error; + setStatusError(tr("Failed to load unsigned transactions")); } // Check tx data and construct confirmation message @@ -961,8 +979,7 @@ UnsignedTransaction *WalletImpl::loadUnsignedTx(const std::string &unsigned_file if (!transaction->m_unsigned_tx_set.transfers.empty()) extra_message = (boost::format("%u outputs to import. ") % (unsigned)transaction->m_unsigned_tx_set.transfers.size()).str(); transaction->checkLoadedTx([&transaction](){return transaction->m_unsigned_tx_set.txes.size();}, [&transaction](size_t n)->const tools::wallet2::tx_construction_data&{return transaction->m_unsigned_tx_set.txes[n];}, extra_message); - m_status = transaction->status(); - m_errorString = transaction->errorString(); + setStatus(transaction->status(), transaction->errorString()); return transaction; } @@ -973,14 +990,12 @@ bool WalletImpl::submitTransaction(const string &fileName) { bool r = m_wallet->load_tx(fileName, transaction->m_pending_tx); if (!r) { - m_errorString = tr("Failed to load transaction from file"); - m_status = Status_Ok; + setStatus(Status_Ok, tr("Failed to load transaction from file")); return false; } if(!transaction->commit()) { - m_errorString = transaction->m_errorString; - m_status = Status_Error; + setStatusError(transaction->m_errorString); return false; } @@ -991,8 +1006,7 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (m_wallet->watch_only()) { - m_errorString = tr("Wallet is view only"); - m_status = Status_Error; + setStatusError(tr("Wallet is view only")); return false; } @@ -1000,16 +1014,14 @@ bool WalletImpl::exportKeyImages(const string &filename) { if (!m_wallet->export_key_images(filename)) { - m_errorString = tr("failed to save file ") + filename; - m_status = Status_Error; + setStatusError(tr("failed to save file ") + filename); return false; } } catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); return false; } return true; @@ -1018,8 +1030,7 @@ bool WalletImpl::exportKeyImages(const string &filename) bool WalletImpl::importKeyImages(const string &filename) { if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Key images can only be imported with a trusted daemon"); + setStatusError(tr("Key images can only be imported with a trusted daemon")); return false; } try @@ -1032,8 +1043,7 @@ bool WalletImpl::importKeyImages(const string &filename) catch (const std::exception &e) { LOG_ERROR("Error exporting key images: " << e.what()); - m_errorString = string(tr("Failed to import key images: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to import key images: ")) + e.what()); return false; } @@ -1065,8 +1075,7 @@ std::string WalletImpl::getSubaddressLabel(uint32_t accountIndex, uint32_t addre catch (const std::exception &e) { LOG_ERROR("Error getting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to get subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to get subaddress label: ")) + e.what()); return ""; } } @@ -1079,11 +1088,136 @@ void WalletImpl::setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex catch (const std::exception &e) { LOG_ERROR("Error setting subaddress label: ") << e.what(); - m_errorString = string(tr("Failed to set subaddress label: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("Failed to set subaddress label: ")) + e.what()); } } +MultisigState WalletImpl::multisig() const { + MultisigState state; + state.isMultisig = m_wallet->multisig(&state.isReady, &state.threshold, &state.total); + + return state; +} + +string WalletImpl::getMultisigInfo() const { + try { + clearStatus(); + return m_wallet->get_multisig_info(); + } catch (const exception& e) { + LOG_ERROR("Error on generating multisig info: ") << e.what(); + setStatusError(string(tr("Failed to get multisig info: ")) + e.what()); + } + + return string(); +} + +string WalletImpl::makeMultisig(const vector<string>& info, uint32_t threshold) { + try { + clearStatus(); + + if (m_wallet->multisig()) { + throw runtime_error("Wallet is already multisig"); + } + + return m_wallet->make_multisig(epee::wipeable_string(m_password), info, threshold); + } catch (const exception& e) { + LOG_ERROR("Error on making multisig wallet: ") << e.what(); + setStatusError(string(tr("Failed to make multisig: ")) + e.what()); + } + + return string(); +} + +bool WalletImpl::finalizeMultisig(const vector<string>& extraMultisigInfo) { + try { + clearStatus(); + checkMultisigWalletNotReady(m_wallet); + + if (m_wallet->finalize_multisig(epee::wipeable_string(m_password), extraMultisigInfo)) { + return true; + } + + setStatusError(tr("Failed to finalize multisig wallet creation")); + } catch (const exception& e) { + LOG_ERROR("Error on finalizing multisig wallet creation: ") << e.what(); + setStatusError(string(tr("Failed to finalize multisig wallet creation: ")) + e.what()); + } + + return false; +} + +bool WalletImpl::exportMultisigImages(string& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + auto blob = m_wallet->export_multisig(); + images = epee::string_tools::buff_to_hex_nodelimer(blob); + return true; + } catch (const exception& e) { + LOG_ERROR("Error on exporting multisig images: ") << e.what(); + setStatusError(string(tr("Failed to export multisig images: ")) + e.what()); + } + + return false; +} + +size_t WalletImpl::importMultisigImages(const vector<string>& images) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + std::vector<std::string> blobs; + blobs.reserve(images.size()); + + for (const auto& image: images) { + std::string blob; + if (!epee::string_tools::parse_hexstr_to_binbuff(image, blob)) { + LOG_ERROR("Failed to parse imported multisig images"); + setStatusError(tr("Failed to parse imported multisig images")); + return 0; + } + + blobs.emplace_back(std::move(blob)); + } + + return m_wallet->import_multisig(blobs); + } catch (const exception& e) { + LOG_ERROR("Error on importing multisig images: ") << e.what(); + setStatusError(string(tr("Failed to import multisig images: ")) + e.what()); + } + + return 0; +} + +PendingTransaction* WalletImpl::restoreMultisigTransaction(const string& signData) { + try { + clearStatus(); + checkMultisigWalletReady(m_wallet); + + string binary; + if (!epee::string_tools::parse_hexstr_to_binbuff(signData, binary)) { + throw runtime_error("Failed to deserialize multisig transaction"); + } + + tools::wallet2::multisig_tx_set txSet; + if (!m_wallet->load_multisig_tx(binary, txSet, {})) { + throw runtime_error("couldn't parse multisig transaction data"); + } + + auto ptx = new PendingTransactionImpl(*this); + ptx->m_pending_tx = txSet.m_ptx; + ptx->m_signers = txSet.m_signers; + + return ptx; + } catch (exception& e) { + LOG_ERROR("Error on restoring multisig transaction: ") << e.what(); + setStatusError(string(tr("Failed to restore multisig transaction: ")) + e.what()); + } + + return nullptr; +} + // TODO: // 1 - properly handle payment id (add another menthod with explicit 'payment_id' param) // 2 - check / design how "Transaction" can be single interface @@ -1117,8 +1251,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const do { if(!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), dst_addr)) { // TODO: copy-paste 'if treating as an address fails, try as url' from simplewallet.cpp:1982 - m_status = Status_Error; - m_errorString = "Invalid destination address"; + setStatusError(tr("Invalid destination address")); break; } @@ -1143,8 +1276,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const } if (!r) { - m_status = Status_Error; - m_errorString = tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id; + setStatusError(tr("payment id has invalid format, expected 16 or 64 character hex string: ") + payment_id); break; } } @@ -1153,8 +1285,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const set_encrypted_payment_id_to_tx_extra_nonce(extra_nonce, info.payment_id); bool r = add_extra_nonce_to_tx_extra(extra, extra_nonce); if (!r) { - m_status = Status_Error; - m_errorString = tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id); + setStatusError(tr("Failed to add short payment id: ") + epee::string_tools::pod_to_hex(info.payment_id)); break; } } @@ -1185,40 +1316,33 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const extra, subaddr_account, subaddr_indices, m_trustedDaemon); } + if (multisig().isMultisig) { + transaction->m_signers = m_wallet->make_multisig_tx_set(transaction->m_pending_tx).m_signers; + } } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error &e) { - m_errorString = (boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str(); - m_status = Status_Error; - + setStatusError((boost::format(tr("failed to get random outputs to mix: %s")) % e.what()).str()); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1226,8 +1350,7 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; @@ -1235,42 +1358,31 @@ PendingTransaction *WalletImpl::createTransaction(const string &dst_addr, const writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } writer << "\n" << tr("Please sweep unmixable outputs."); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); // Resume refresh thread startRefresh(); return transaction; @@ -1291,38 +1403,31 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() } catch (const tools::error::daemon_busy&) { // TODO: make it translatable with "tr"? - m_errorString = tr("daemon is busy. Please try again later."); - m_status = Status_Error; + setStatusError(tr("daemon is busy. Please try again later.")); } catch (const tools::error::no_connection_to_daemon&) { - m_errorString = tr("no connection to daemon. Please make sure daemon is running."); - m_status = Status_Error; + setStatusError(tr("no connection to daemon. Please make sure daemon is running.")); } catch (const tools::error::wallet_rpc_error& e) { - m_errorString = tr("RPC error: ") + e.to_string(); - m_status = Status_Error; + setStatusError(tr("RPC error: ") + e.to_string()); } catch (const tools::error::get_random_outs_error&) { - m_errorString = tr("failed to get random outputs to mix"); - m_status = Status_Error; - + setStatusError(tr("failed to get random outputs to mix")); } catch (const tools::error::not_enough_unlocked_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_money& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, overall balance only %s, sent amount %s")) % print_money(e.available()) % print_money(e.tx_amount()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::tx_not_possible& e) { - m_status = Status_Error; + setStatusError(""); std::ostringstream writer; writer << boost::format(tr("not enough money to transfer, available only %s, transaction amount %s = %s + %s (fee)")) % @@ -1330,50 +1435,38 @@ PendingTransaction *WalletImpl::createSweepUnmixableTransaction() print_money(e.tx_amount() + e.fee()) % print_money(e.tx_amount()) % print_money(e.fee()); - m_errorString = writer.str(); - + setStatusError(writer.str()); } catch (const tools::error::not_enough_outs_to_mix& e) { std::ostringstream writer; writer << tr("not enough outputs for specified ring size") << " = " << (e.mixin_count() + 1) << ":"; for (const std::pair<uint64_t, uint64_t> outs_for_amount : e.scanty_outs()) { writer << "\n" << tr("output amount") << " = " << print_money(outs_for_amount.first) << ", " << tr("found outputs to use") << " = " << outs_for_amount.second; } - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_not_constructed&) { - m_errorString = tr("transaction was not constructed"); - m_status = Status_Error; + setStatusError(tr("transaction was not constructed")); } catch (const tools::error::tx_rejected& e) { std::ostringstream writer; writer << (boost::format(tr("transaction %s was rejected by daemon with status: ")) % get_transaction_hash(e.tx())) << e.status(); - m_errorString = writer.str(); - m_status = Status_Error; + setStatusError(writer.str()); } catch (const tools::error::tx_sum_overflow& e) { - m_errorString = e.what(); - m_status = Status_Error; + setStatusError(e.what()); } catch (const tools::error::zero_destination&) { - m_errorString = tr("one of destinations is zero"); - m_status = Status_Error; + setStatusError(tr("one of destinations is zero")); } catch (const tools::error::tx_too_big& e) { - m_errorString = tr("failed to find a suitable way to split transactions"); - m_status = Status_Error; + setStatusError(tr("failed to find a suitable way to split transactions")); } catch (const tools::error::transfer_error& e) { - m_errorString = string(tr("unknown transfer error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unknown transfer error: ")) + e.what()); } catch (const tools::error::wallet_internal_error& e) { - m_errorString = string(tr("internal error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("internal error: ")) + e.what()); } catch (const std::exception& e) { - m_errorString = string(tr("unexpected error: ")) + e.what(); - m_status = Status_Error; + setStatusError(string(tr("unexpected error: ")) + e.what()); } catch (...) { - m_errorString = tr("unknown error"); - m_status = Status_Error; + setStatusError(tr("unknown error")); } } while (false); - transaction->m_status = m_status; - transaction->m_errorString = m_errorString; + statusWithErrorString(transaction->m_status, transaction->m_errorString); return transaction; } @@ -1444,8 +1537,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } @@ -1453,7 +1545,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const std::vector<crypto::secret_key> additional_tx_keys; if (m_wallet->get_tx_key(txid, tx_key, additional_tx_keys)) { - m_status = Status_Ok; + clearStatus(); std::ostringstream oss; oss << epee::string_tools::pod_to_hex(tx_key); for (size_t i = 0; i < additional_tx_keys.size(); ++i) @@ -1462,8 +1554,7 @@ std::string WalletImpl::getTxKey(const std::string &txid_str) const } else { - m_status = Status_Error; - m_errorString = tr("no tx keys found for this txid"); + setStatusError(tr("no tx keys found for this txid")); return ""; } } @@ -1473,8 +1564,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } @@ -1482,8 +1572,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, std::vector<crypto::secret_key> additional_tx_keys; if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), tx_key)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1492,8 +1581,7 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, additional_tx_keys.resize(additional_tx_keys.size() + 1); if (!epee::string_tools::hex_to_pod(tx_key_str.substr(0, 64), additional_tx_keys.back())) { - m_status = Status_Error; - m_errorString = tr("Failed to parse tx key"); + setStatusError(tr("Failed to parse tx key")); return false; } tx_key_str = tx_key_str.substr(64); @@ -1502,21 +1590,19 @@ bool WalletImpl::checkTxKey(const std::string &txid_str, std::string tx_key_str, cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { m_wallet->check_tx_key(txid, tx_key, additional_tx_keys, info.address, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1526,28 +1612,25 @@ std::string WalletImpl::getTxProof(const std::string &txid_str, const std::strin crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_tx_proof(txid, info.address, info.is_subaddress, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1557,29 +1640,26 @@ bool WalletImpl::checkTxProof(const std::string &txid_str, const std::string &ad crypto::hash txid; if (!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address_str)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } try { good = m_wallet->check_tx_proof(txid, info.address, info.is_subaddress, message, signature, received, in_pool, confirmations); - m_status = Status_Ok; + clearStatus(); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1588,20 +1668,18 @@ std::string WalletImpl::getSpendProof(const std::string &txid_str, const std::st crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return ""; } try { - m_status = Status_Ok; + clearStatus(); return m_wallet->get_spend_proof(txid, message); } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1611,21 +1689,19 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string crypto::hash txid; if(!epee::string_tools::hex_to_pod(txid_str, txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_spend_proof(txid, message, signature); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1633,7 +1709,7 @@ bool WalletImpl::checkSpendProof(const std::string &txid_str, const std::string std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64_t amount, const std::string &message) const { try { - m_status = Status_Ok; + clearStatus(); boost::optional<std::pair<uint32_t, uint64_t>> account_minreserve; if (!all) { @@ -1643,8 +1719,7 @@ std::string WalletImpl::getReserveProof(bool all, uint32_t account_index, uint64 } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return ""; } } @@ -1653,28 +1728,25 @@ bool WalletImpl::checkReserveProof(const std::string &address, const std::string cryptonote::address_parse_info info; if (!cryptonote::get_account_address_from_str(info, m_wallet->nettype(), address)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse address"); + setStatusError(tr("Failed to parse address")); return false; } if (info.is_subaddress) { - m_status = Status_Error; - m_errorString = tr("Address must not be a subaddress"); + setStatusError(tr("Address must not be a subaddress")); return false; } good = false; try { - m_status = Status_Ok; + clearStatus(); good = m_wallet->check_reserve_proof(info.address, message, signature, total, spent); return true; } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } } @@ -1697,10 +1769,10 @@ bool WalletImpl::verifySignedMessage(const std::string &message, const std::stri bool WalletImpl::connectToDaemon() { bool result = m_wallet->check_connection(NULL, DEFAULT_CONNECTION_TIMEOUT_MILLIS); - m_status = result ? Status_Ok : Status_Error; if (!result) { - m_errorString = "Error connecting to daemon at " + m_wallet->get_daemon_address(); + setStatusError("Error connecting to daemon at " + m_wallet->get_daemon_address()); } else { + clearStatus(); // start refreshing here } return result; @@ -1735,10 +1807,28 @@ bool WalletImpl::watchOnly() const void WalletImpl::clearStatus() const { + boost::lock_guard<boost::mutex> l(m_statusMutex); m_status = Status_Ok; m_errorString.clear(); } +void WalletImpl::setStatusError(const std::string& message) const +{ + setStatus(Status_Error, message); +} + +void WalletImpl::setStatusCritical(const std::string& message) const +{ + setStatus(Status_Critical, message); +} + +void WalletImpl::setStatus(int status, const std::string& message) const +{ + boost::lock_guard<boost::mutex> l(m_statusMutex); + m_status = status; + m_errorString = message; +} + void WalletImpl::refreshThreadFunc() { LOG_PRINT_L3(__FUNCTION__ << ": starting refresh thread"); @@ -1760,7 +1850,7 @@ void WalletImpl::refreshThreadFunc() LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired..."); LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled); - LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status); + LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << status()); if (m_refreshEnabled) { LOG_PRINT_L3(__FUNCTION__ << ": refreshing..."); doRefresh(); @@ -1792,8 +1882,7 @@ void WalletImpl::doRefresh() LOG_PRINT_L3(__FUNCTION__ << ": skipping refresh - daemon is not synced"); } } catch (const std::exception &e) { - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); } if (m_wallet2Callback->getListener()) { m_wallet2Callback->getListener()->refreshed(); @@ -1882,16 +1971,14 @@ bool WalletImpl::rescanSpent() { clearStatus(); if (!trustedDaemon()) { - m_status = Status_Error; - m_errorString = tr("Rescan spent can only be used with a trusted daemon"); + setStatusError(tr("Rescan spent can only be used with a trusted daemon")); return false; } try { m_wallet->rescan_spent(); } catch (const std::exception &e) { LOG_ERROR(__FUNCTION__ << " error: " << e.what()); - m_status = Status_Error; - m_errorString = e.what(); + setStatusError(e.what()); return false; } return true; @@ -1917,8 +2004,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool crypto::public_key pkey; if (!epee::string_tools::hex_to_pod(str, pkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } raw_pubkeys.push_back(pkey); @@ -1926,8 +2012,7 @@ bool WalletImpl::blackballOutputs(const std::vector<std::string> &pubkeys, bool bool ret = m_wallet->set_blackballed_outputs(raw_pubkeys, add); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set blackballed outputs"); + setStatusError(tr("Failed to set blackballed outputs")); return false; } return true; @@ -1938,15 +2023,13 @@ bool WalletImpl::unblackballOutput(const std::string &pubkey) crypto::public_key raw_pubkey; if (!epee::string_tools::hex_to_pod(pubkey, raw_pubkey)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse output public key"); + setStatusError(tr("Failed to parse output public key")); return false; } bool ret = m_wallet->unblackball_output(raw_pubkey); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to unblackball output"); + setStatusError(tr("Failed to unblackball output")); return false; } return true; @@ -1957,15 +2040,13 @@ bool WalletImpl::getRing(const std::string &key_image, std::vector<uint64_t> &ri crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->get_ring(raw_key_image, ring); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get ring"); + setStatusError(tr("Failed to get ring")); return false; } return true; @@ -1976,16 +2057,14 @@ bool WalletImpl::getRings(const std::string &txid, std::vector<std::pair<std::st crypto::hash raw_txid; if (!epee::string_tools::hex_to_pod(txid, raw_txid)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse txid"); + setStatusError(tr("Failed to parse txid")); return false; } std::vector<std::pair<crypto::key_image, std::vector<uint64_t>>> raw_rings; bool ret = m_wallet->get_rings(raw_txid, raw_rings); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to get rings"); + setStatusError(tr("Failed to get rings")); return false; } for (const auto &r: raw_rings) @@ -2000,15 +2079,13 @@ bool WalletImpl::setRing(const std::string &key_image, const std::vector<uint64_ crypto::key_image raw_key_image; if (!epee::string_tools::hex_to_pod(key_image, raw_key_image)) { - m_status = Status_Error; - m_errorString = tr("Failed to parse key image"); + setStatusError(tr("Failed to parse key image")); return false; } bool ret = m_wallet->set_ring(raw_key_image, ring, relative); if (!ret) { - m_status = Status_Error; - m_errorString = tr("Failed to set ring"); + setStatusError(tr("Failed to set ring")); return false; } return true; diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h index 4929c9673..e0e627c36 100644 --- a/src/wallet/api/wallet.h +++ b/src/wallet/api/wallet.h @@ -83,6 +83,7 @@ public: // void setListener(Listener *) {} int status() const; std::string errorString() const; + void statusWithErrorString(int& status, std::string& errorString) const override; bool setPassword(const std::string &password); std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const; std::string integratedAddress(const std::string &payment_id) const; @@ -126,6 +127,14 @@ public: std::string getSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex) const; void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label); + MultisigState multisig() const override; + std::string getMultisigInfo() const override; + std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) override; + bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) override; + bool exportMultisigImages(std::string& images) override; + size_t importMultisigImages(const std::vector<std::string>& images) override; + PendingTransaction* restoreMultisigTransaction(const std::string& signData) override; + PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id, optional<uint64_t> amount, uint32_t mixin_count, PendingTransaction::Priority priority = PendingTransaction::Priority_Low, @@ -174,6 +183,9 @@ public: private: void clearStatus() const; + void setStatusError(const std::string& message) const; + void setStatusCritical(const std::string& message) const; + void setStatus(int status, const std::string& message) const; void refreshThreadFunc(); void doRefresh(); bool daemonSynced() const; @@ -191,7 +203,8 @@ private: friend class SubaddressAccountImpl; tools::wallet2 * m_wallet; - mutable std::atomic<int> m_status; + mutable boost::mutex m_statusMutex; + mutable int m_status; mutable std::string m_errorString; std::string m_password; TransactionHistoryImpl * m_history; diff --git a/src/wallet/api/wallet2_api.h b/src/wallet/api/wallet2_api.h index 4fbc7298a..27d290e68 100644 --- a/src/wallet/api/wallet2_api.h +++ b/src/wallet/api/wallet2_api.h @@ -100,6 +100,30 @@ struct PendingTransaction virtual uint64_t txCount() const = 0; virtual std::vector<uint32_t> subaddrAccount() const = 0; virtual std::vector<std::set<uint32_t>> subaddrIndices() const = 0; + + /** + * @brief multisigSignData + * @return encoded multisig transaction with signers' keys. + * Transfer this data to another wallet participant to sign it. + * Assumed use case is: + * 1. Initiator: + * auto data = pendingTransaction->multisigSignData(); + * 2. Signer1: + * pendingTransaction = wallet->restoreMultisigTransaction(data); + * pendingTransaction->signMultisigTx(); + * auto signed = pendingTransaction->multisigSignData(); + * 3. Signer2: + * pendingTransaction = wallet->restoreMultisigTransaction(signed); + * pendingTransaction->signMultisigTx(); + * pendingTransaction->commit(); + */ + virtual std::string multisigSignData() = 0; + virtual void signMultisigTx() = 0; + /** + * @brief signersKeys + * @return vector of base58-encoded signers' public keys + */ + virtual std::vector<std::string> signersKeys() const = 0; }; /** @@ -291,6 +315,15 @@ struct SubaddressAccount virtual void refresh() = 0; }; +struct MultisigState { + MultisigState() : isMultisig(false), isReady(false), threshold(0), total(0) {} + + bool isMultisig; + bool isReady; + uint32_t threshold; + uint32_t total; +}; + struct WalletListener { virtual ~WalletListener() = 0; @@ -358,9 +391,11 @@ struct Wallet virtual std::string getSeedLanguage() const = 0; virtual void setSeedLanguage(const std::string &arg) = 0; //! returns wallet status (Status_Ok | Status_Error) - virtual int status() const = 0; + virtual int status() const = 0; //deprecated: use safe alternative statusWithErrorString //! in case error status, returns error string - virtual std::string errorString() const = 0; + virtual std::string errorString() const = 0; //deprecated: use safe alternative statusWithErrorString + //! returns both error and error string atomically. suggested to use in instead of status() and errorString() + virtual void statusWithErrorString(int& status, std::string& errorString) const = 0; virtual bool setPassword(const std::string &password) = 0; virtual std::string address(uint32_t accountIndex = 0, uint32_t addressIndex = 0) const = 0; std::string mainAddress() const { return address(0, 0); } @@ -629,6 +664,48 @@ struct Wallet */ virtual void setSubaddressLabel(uint32_t accountIndex, uint32_t addressIndex, const std::string &label) = 0; + /** + * @brief multisig - returns current state of multisig wallet creation process + * @return MultisigState struct + */ + virtual MultisigState multisig() const = 0; + /** + * @brief getMultisigInfo + * @return serialized and signed multisig info string + */ + virtual std::string getMultisigInfo() const = 0; + /** + * @brief makeMultisig - switches wallet in multisig state. The one and only creation phase for N / N wallets + * @param info - vector of multisig infos from other participants obtained with getMulitisInfo call + * @param threshold - number of required signers to make valid transaction. Must be equal to number of participants (N) or N - 1 + * @return in case of N / N wallets returns empty string since no more key exchanges needed. For N - 1 / N wallets returns base58 encoded extra multisig info + */ + virtual std::string makeMultisig(const std::vector<std::string>& info, uint32_t threshold) = 0; + /** + * @brief finalizeMultisig - finalizes N - 1 / N multisig wallets creation + * @param extraMultisigInfo - wallet participants' extra multisig info obtained with makeMultisig call + * @return true if success + */ + virtual bool finalizeMultisig(const std::vector<std::string>& extraMultisigInfo) = 0; + /** + * @brief exportMultisigImages - exports transfers' key images + * @param images - output paramter for hex encoded array of images + * @return true if success + */ + virtual bool exportMultisigImages(std::string& images) = 0; + /** + * @brief importMultisigImages - imports other participants' multisig images + * @param images - array of hex encoded arrays of images obtained with exportMultisigImages + * @return number of imported images + */ + virtual size_t importMultisigImages(const std::vector<std::string>& images) = 0; + + /** + * @brief restoreMultisigTransaction creates PendingTransaction from signData + * @param signData encrypted unsigned transaction. Obtained with PendingTransaction::multisigSignData + * @return PendingTransaction + */ + virtual PendingTransaction* restoreMultisigTransaction(const std::string& signData) = 0; /*! * \brief createTransaction creates transaction. if dst_addr is an integrated address, payment_id is ignored * \param dst_addr destination address as string diff --git a/src/wallet/ringdb.cpp b/src/wallet/ringdb.cpp index 44992520f..3f2634c8b 100644 --- a/src/wallet/ringdb.cpp +++ b/src/wallet/ringdb.cpp @@ -190,7 +190,8 @@ namespace tools { ringdb::ringdb(std::string filename, const std::string &genesis): - filename(filename) + filename(filename), + env(NULL) { MDB_txn *txn; bool tx_active = false; @@ -227,9 +228,18 @@ ringdb::ringdb(std::string filename, const std::string &genesis): ringdb::~ringdb() { - mdb_dbi_close(env, dbi_rings); - mdb_dbi_close(env, dbi_blackballs); - mdb_env_close(env); + close(); +} + +void ringdb::close() +{ + if (env) + { + mdb_dbi_close(env, dbi_rings); + mdb_dbi_close(env, dbi_blackballs); + mdb_env_close(env); + env = NULL; + } } bool ringdb::add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx) diff --git a/src/wallet/ringdb.h b/src/wallet/ringdb.h index 2bd1ac149..6b4bce124 100644 --- a/src/wallet/ringdb.h +++ b/src/wallet/ringdb.h @@ -41,6 +41,7 @@ namespace tools { public: ringdb(std::string filename, const std::string &genesis); + void close(); ~ringdb(); bool add_rings(const crypto::chacha_key &chacha_key, const cryptonote::transaction_prefix &tx); diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp index cd3ca3245..3af449455 100644 --- a/src/wallet/wallet2.cpp +++ b/src/wallet/wallet2.cpp @@ -141,13 +141,15 @@ struct options { const command_line::arg_descriptor<bool> testnet = {"testnet", tools::wallet2::tr("For testnet. Daemon must also be launched with --testnet flag"), false}; const command_line::arg_descriptor<bool> stagenet = {"stagenet", tools::wallet2::tr("For stagenet. Daemon must also be launched with --stagenet flag"), false}; const command_line::arg_descriptor<bool> restricted = {"restricted-rpc", tools::wallet2::tr("Restricts to view-only commands"), false}; - const command_line::arg_descriptor<std::string, false, true> shared_ringdb_dir = { + const command_line::arg_descriptor<std::string, false, true, 2> shared_ringdb_dir = { "shared-ringdb-dir", tools::wallet2::tr("Set shared ring database path"), get_default_ringdb_path(), - testnet, - [](bool testnet, bool defaulted, std::string val)->std::string { - if (testnet) + {{ &testnet, &stagenet }}, + [](std::array<bool, 2> testnet_stagenet, bool defaulted, std::string val)->std::string { + if (testnet_stagenet[0]) return (boost::filesystem::path(val) / "testnet").string(); + else if (testnet_stagenet[1]) + return (boost::filesystem::path(val) / "stagenet").string(); return val; } }; @@ -2883,6 +2885,7 @@ bool wallet2::verify_password(const epee::wipeable_string& password) const * \param keys_file_name Keys file to verify password for * \param password Password to verify * \param no_spend_key If set = only verify view keys, otherwise also spend keys + * \param hwdev The hardware device to use * \return true if password is correct * * for verification only @@ -2933,9 +2936,10 @@ bool wallet2::verify_password(const std::string& keys_file_name, const epee::wip /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param multisig_data The multisig restore info and keys + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param multisig_data The multisig restore info and keys + * \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const std::string& multisig_data, bool create_address_file) @@ -3028,12 +3032,13 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Generates a wallet or restores one. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param recovery_param If it is a restore, the recovery key - * \param recover Whether it is a restore - * \param two_random Whether it is a non-deterministic wallet - * \return The secret key of the generated wallet + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param recovery_param If it is a restore, the recovery key + * \param recover Whether it is a restore + * \param two_random Whether it is a non-deterministic wallet + * \param create_address_file Whether to create an address file + * \return The secret key of the generated wallet */ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const crypto::secret_key& recovery_param, bool recover, bool two_random, bool create_address_file) @@ -3129,9 +3134,11 @@ crypto::secret_key wallet2::generate(const std::string& wallet_, const epee::wip /*! * \brief Creates a watch only wallet from a public address and a view secret key. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3178,10 +3185,12 @@ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. -* \param wallet_ Name of wallet file -* \param password Password of wallet file -* \param spendkey spend secret key -* \param viewkey view secret key +* \param wallet_ Name of wallet file +* \param password Password of wallet file +* \param account_public_address The account's public address +* \param spendkey spend secret key +* \param viewkey view secret key +* \param create_address_file Whether to create an address file */ void wallet2::generate(const std::string& wallet_, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -3628,8 +3637,9 @@ void wallet2::rewrite(const std::string& wallet_name, const epee::wipeable_strin } /*! * \brief Writes to a file named based on the normal wallet (doesn't generate key, assumes it's already there) - * \param wallet_name Base name of wallet file - * \param password Password for wallet file + * \param wallet_name Base name of wallet file + * \param password Password for wallet file + * \param new_keys_filename [OUT] Name of new keys file */ void wallet2::write_watch_only_wallet(const std::string& wallet_name, const epee::wipeable_string& password, std::string &new_keys_filename) { @@ -5013,7 +5023,7 @@ bool wallet2::save_multisig_tx(const multisig_tx_set &txs, const std::string &fi return epee::file_io_utils::save_string_to_file(filename, ciphertext); } //---------------------------------------------------------------------------------------------------- -std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +wallet2::multisig_tx_set wallet2::make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const { multisig_tx_set txs; txs.m_ptx = ptx_vector; @@ -5025,8 +5035,12 @@ std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) } txs.m_signers.insert(get_multisig_signer_public_key()); + return txs; +} - return save_multisig_tx(txs); +std::string wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector) +{ + return save_multisig_tx(make_multisig_tx_set(ptx_vector)); } //---------------------------------------------------------------------------------------------------- bool wallet2::save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename) diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h index 69b63876a..5e0dd076b 100644 --- a/src/wallet/wallet2.h +++ b/src/wallet/wallet2.h @@ -477,21 +477,23 @@ namespace tools bool two_random = false, bool create_address_file = false); /*! * \brief Creates a wallet from a public address and a spend/view secret key pair. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param spendkey spend secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param spendkey spend secret key + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, const crypto::secret_key& spendkey, const crypto::secret_key& viewkey, bool create_address_file = false); /*! * \brief Creates a watch only wallet from a public address and a view secret key. - * \param wallet_ Name of wallet file - * \param password Password of wallet file - * \param viewkey view secret key - * \param create_address_file Whether to create an address file + * \param wallet_ Name of wallet file + * \param password Password of wallet file + * \param account_public_address The account's public address + * \param viewkey view secret key + * \param create_address_file Whether to create an address file */ void generate(const std::string& wallet, const epee::wipeable_string& password, const cryptonote::account_public_address &account_public_address, @@ -561,9 +563,9 @@ namespace tools void load(const std::string& wallet, const epee::wipeable_string& password); void store(); /*! - * \brief store_to - stores wallet to another file(s), deleting old ones - * \param path - path to the wallet file (keys and address filenames will be generated based on this filename) - * \param password - password to protect new wallet (TODO: probably better save the password in the wallet object?) + * \brief store_to Stores wallet to another file(s), deleting old ones + * \param path Path to the wallet file (keys and address filenames will be generated based on this filename) + * \param password Password to protect new wallet (TODO: probably better save the password in the wallet object?) */ void store_to(const std::string &path, const epee::wipeable_string &password); @@ -686,6 +688,7 @@ namespace tools bool save_multisig_tx(const multisig_tx_set &txs, const std::string &filename); std::string save_multisig_tx(const std::vector<pending_tx>& ptx_vector); bool save_multisig_tx(const std::vector<pending_tx>& ptx_vector, const std::string &filename); + multisig_tx_set make_multisig_tx_set(const std::vector<pending_tx>& ptx_vector) const; // load unsigned tx from file and sign it. Takes confirmation callback as argument. Used by the cli wallet bool sign_tx(const std::string &unsigned_filename, const std::string &signed_filename, std::vector<wallet2::pending_tx> &ptx, std::function<bool(const unsigned_tx_set&)> accept_func = NULL, bool export_raw = false); // sign unsigned tx. Takes unsigned_tx_set as argument. Used by GUI @@ -959,7 +962,7 @@ namespace tools /*! * \brief Set the label of the given tag. * \param tag Tag's name (which must be non-empty). - * \param label Tag's description. + * \param description Tag's description. */ void set_account_tag_description(const std::string& tag, const std::string& description); diff --git a/src/wallet/wallet_args.cpp b/src/wallet/wallet_args.cpp index a6ff63dd3..6311e7700 100644 --- a/src/wallet/wallet_args.cpp +++ b/src/wallet/wallet_args.cpp @@ -82,7 +82,7 @@ namespace wallet_args return i18n_translate(str, "wallet_args"); } - boost::optional<boost::program_options::variables_map> main( + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, @@ -127,6 +127,7 @@ namespace wallet_args po::options_description desc_all; desc_all.add(desc_general).add(desc_params); po::variables_map vm; + bool should_terminate = false; bool r = command_line::handle_error_helper(desc_all, [&]() { auto parser = po::command_line_parser(argc, argv).options(desc_all).positional(positional_options); @@ -139,12 +140,14 @@ namespace wallet_args "daemon to work correctly.") << ENDL; Print(print) << wallet_args::tr("Usage:") << ENDL << " " << usage; Print(print) << desc_all; - return false; + should_terminate = true; + return true; } else if (command_line::get_arg(vm, command_line::arg_version)) { Print(print) << "Monero '" << MONERO_RELEASE_NAME << "' (v" << MONERO_VERSION_FULL << ")"; - return false; + should_terminate = true; + return true; } if(command_line::has_arg(vm, arg_config_file)) @@ -167,7 +170,10 @@ namespace wallet_args return true; }); if (!r) - return boost::none; + return {boost::none, true}; + + if (should_terminate) + return {std::move(vm), should_terminate}; std::string log_path; if (!command_line::is_arg_defaulted(vm, arg_log_file)) @@ -196,6 +202,6 @@ namespace wallet_args Print(print) << boost::format(wallet_args::tr("Logging to %s")) % log_path; - return {std::move(vm)}; + return {std::move(vm), should_terminate}; } } diff --git a/src/wallet/wallet_args.h b/src/wallet/wallet_args.h index af6685845..a1f251144 100644 --- a/src/wallet/wallet_args.h +++ b/src/wallet/wallet_args.h @@ -44,8 +44,11 @@ namespace wallet_args concurrency. Log file and concurrency arguments are handled, along with basic global init for the wallet process. - \return The list of parsed options, iff there are no errors.*/ - boost::optional<boost::program_options::variables_map> main( + \return + pair.first: The list of parsed options, iff there are no errors. + pair.second: Should the execution terminate succesfully without actually launching the application + */ + std::pair<boost::optional<boost::program_options::variables_map>, bool> main( int argc, char** argv, const char* const usage, const char* const notice, diff --git a/src/wallet/wallet_rpc_server.cpp b/src/wallet/wallet_rpc_server.cpp index a9d211532..dc1beef7b 100644 --- a/src/wallet/wallet_rpc_server.cpp +++ b/src/wallet/wallet_rpc_server.cpp @@ -2876,6 +2876,12 @@ namespace tools return true; } //------------------------------------------------------------------------------------------------------------------------------ + bool wallet_rpc_server::on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er) + { + res.version = WALLET_RPC_VERSION; + return true; + } + //------------------------------------------------------------------------------------------------------------------------------ } int main(int argc, char** argv) { @@ -2895,7 +2901,9 @@ int main(int argc, char** argv) { command_line::add_arg(desc_params, arg_wallet_dir); command_line::add_arg(desc_params, arg_prompt_for_password); - const auto vm = wallet_args::main( + boost::optional<po::variables_map> vm; + bool should_terminate = false; + std::tie(vm, should_terminate) = wallet_args::main( argc, argv, "monero-wallet-rpc [--wallet-file=<file>|--generate-from-json=<file>|--wallet-dir=<directory>] [--rpc-bind-port=<port>]", tools::wallet_rpc_server::tr("This is the RPC monero wallet. It needs to connect to a monero\ndaemon to work correctly."), @@ -2909,6 +2917,10 @@ int main(int argc, char** argv) { { return 1; } + if (should_terminate) + { + return 0; + } std::unique_ptr<tools::wallet2> wal; try diff --git a/src/wallet/wallet_rpc_server.h b/src/wallet/wallet_rpc_server.h index 2ec53cc80..cb1a274b6 100644 --- a/src/wallet/wallet_rpc_server.h +++ b/src/wallet/wallet_rpc_server.h @@ -135,6 +135,7 @@ namespace tools MAP_JON_RPC_WE("finalize_multisig", on_finalize_multisig, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG) MAP_JON_RPC_WE("sign_multisig", on_sign_multisig, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG) MAP_JON_RPC_WE("submit_multisig", on_submit_multisig, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG) + MAP_JON_RPC_WE("get_version", on_get_version, wallet_rpc::COMMAND_RPC_GET_VERSION) END_JSON_RPC_MAP() END_URI_MAP2() @@ -203,6 +204,7 @@ namespace tools bool on_finalize_multisig(const wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_FINALIZE_MULTISIG::response& res, epee::json_rpc::error& er); bool on_sign_multisig(const wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SIGN_MULTISIG::response& res, epee::json_rpc::error& er); bool on_submit_multisig(const wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::request& req, wallet_rpc::COMMAND_RPC_SUBMIT_MULTISIG::response& res, epee::json_rpc::error& er); + bool on_get_version(const wallet_rpc::COMMAND_RPC_GET_VERSION::request& req, wallet_rpc::COMMAND_RPC_GET_VERSION::response& res, epee::json_rpc::error& er); //json rpc v2 bool on_query_key(const wallet_rpc::COMMAND_RPC_QUERY_KEY::request& req, wallet_rpc::COMMAND_RPC_QUERY_KEY::response& res, epee::json_rpc::error& er); diff --git a/src/wallet/wallet_rpc_server_commands_defs.h b/src/wallet/wallet_rpc_server_commands_defs.h index a0f43c9b9..d44aa459f 100644 --- a/src/wallet/wallet_rpc_server_commands_defs.h +++ b/src/wallet/wallet_rpc_server_commands_defs.h @@ -39,6 +39,17 @@ #undef MONERO_DEFAULT_LOG_CATEGORY #define MONERO_DEFAULT_LOG_CATEGORY "wallet.rpc" +// When making *any* change here, bump minor +// If the change is incompatible, then bump major and set minor to 0 +// This ensures WALLET_RPC_VERSION always increases, that every change +// has its own version, and that clients can just test major to see +// whether they can talk to a given wallet without having to know in +// advance which version they will stop working with +// Don't go over 32767 for any of these +#define WALLET_RPC_VERSION_MAJOR 1 +#define WALLET_RPC_VERSION_MINOR 0 +#define MAKE_WALLET_RPC_VERSION(major,minor) (((major)<<16)|(minor)) +#define WALLET_RPC_VERSION MAKE_WALLET_RPC_VERSION(WALLET_RPC_VERSION_MAJOR, WALLET_RPC_VERSION_MINOR) namespace tools { namespace wallet_rpc @@ -1848,5 +1859,23 @@ namespace wallet_rpc }; }; + struct COMMAND_RPC_GET_VERSION + { + struct request + { + BEGIN_KV_SERIALIZE_MAP() + END_KV_SERIALIZE_MAP() + }; + + struct response + { + uint32_t version; + + BEGIN_KV_SERIALIZE_MAP() + KV_SERIALIZE(version) + END_KV_SERIALIZE_MAP() + }; + }; + } } |