aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt11
-rw-r--r--README.md6
-rw-r--r--contrib/rlwrap/monerocommands_bitmonerod.txt35
-rw-r--r--contrib/rlwrap/monerocommands_monero-wallet-cli.txt33
-rw-r--r--src/blockchain_db/lmdb/db_lmdb.cpp6
-rw-r--r--src/common/command_line.cpp6
-rw-r--r--src/common/command_line.h1
-rw-r--r--src/cryptonote_core/cryptonote_core.cpp5
-rw-r--r--src/cryptonote_core/cryptonote_core.h9
-rw-r--r--src/cryptonote_protocol/cryptonote_protocol_handler.inl4
-rw-r--r--src/simplewallet/simplewallet.cpp4
-rw-r--r--src/wallet/api/wallet.cpp69
-rw-r--r--src/wallet/api/wallet.h13
-rw-r--r--src/wallet/wallet2.cpp32
-rw-r--r--src/wallet/wallet2.h1
-rw-r--r--src/wallet/wallet2_api.h57
-rw-r--r--tests/libwallet_api_tests/main.cpp154
-rw-r--r--tests/libwallet_api_tests/scripts/README.md24
-rwxr-xr-xtests/libwallet_api_tests/scripts/create_wallets.sh4
-rwxr-xr-xtests/libwallet_api_tests/scripts/send_funds.sh2
-rw-r--r--utils/gpg_keys/luigi1111.asc30
21 files changed, 442 insertions, 64 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 9f15c73dc..0d82324a3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -65,10 +65,10 @@ else()
set(ARCH_ID "${ARCH}")
endif()
string(TOLOWER "${ARCH_ID}" ARM_ID)
-string(SUBSTRING "${ARCH_ID}" 0 3 ARM_TEST)
+string(SUBSTRING "${ARM_ID}" 0 3 ARM_TEST)
if (ARM_TEST STREQUAL "arm")
set(ARM 1)
- string(SUBSTRING "${ARCH_ID}" 0 5 ARM_TEST)
+ string(SUBSTRING "${ARM_ID}" 0 5 ARM_TEST)
if (ARM_TEST STREQUAL "armv6")
set(ARM6 1)
endif()
@@ -77,7 +77,7 @@ if (ARM_TEST STREQUAL "arm")
endif()
endif()
-if (ARCH_ID STREQUAL "aarch64")
+if (ARM_ID STREQUAL "aarch64")
set(ARM 1)
set(ARM8 1)
endif()
@@ -512,11 +512,6 @@ else()
if(CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# There is a clang bug that does not allow to compile code that uses AES-NI intrinsics if -flto is enabled, so explicitly disable
set(USE_LTO false)
- # explicitly define stdlib for older versions of clang
- if(CMAKE_C_COMPILER_VERSION VERSION_LESS 3.7)
- set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -stdlib=libstdc++")
- set(CMAKE_EXE_LINKER_FLAGS "${CMAKE_EXE_LINKER_FLAGS} -stdlib=libstdc++")
- endif()
endif()
diff --git a/README.md b/README.md
index 5c66289ae..df957d9df 100644
--- a/README.md
+++ b/README.md
@@ -164,11 +164,7 @@ application.
* Download and install the [MSYS2 installer](http://msys2.github.io), either the 64-bit or the 32-bit package, depending on your system.
* Open the MSYS shell via the `MSYS2 Shell` shortcut
-* Update the core packages in your MSYS2 install:
-
- update-core
-
-* Exit the MSYS shell using Alt+F4, then restart MSYS and update packages using pacman:
+* Update packages using pacman:
pacman -Syuu
diff --git a/contrib/rlwrap/monerocommands_bitmonerod.txt b/contrib/rlwrap/monerocommands_bitmonerod.txt
new file mode 100644
index 000000000..c14d28e71
--- /dev/null
+++ b/contrib/rlwrap/monerocommands_bitmonerod.txt
@@ -0,0 +1,35 @@
+ban
+bans
+diff
+exit
+fast_exit
+flush_txpool
+hard_fork_info
+help
+hide_hr
+is_key_image_spent
+limit
+limit_down
+limit_up
+out_peers
+output_histogram
+print_bc
+print_block
+print_cn
+print_height
+print_pl
+print_pool
+print_pool_sh
+print_status
+print_tx
+q
+save
+set_log
+show_hr
+start_mining
+start_save_graph
+status
+stop_daemon
+stop_mining
+stop_save_graph
+unban
diff --git a/contrib/rlwrap/monerocommands_monero-wallet-cli.txt b/contrib/rlwrap/monerocommands_monero-wallet-cli.txt
new file mode 100644
index 000000000..c5e4c0323
--- /dev/null
+++ b/contrib/rlwrap/monerocommands_monero-wallet-cli.txt
@@ -0,0 +1,33 @@
+address
+balance
+bc_height
+check_tx_key
+export_key_images
+get_tx_key
+get_tx_note
+help
+import_key_images
+incoming_transfers
+integrated_address
+payments
+refresh
+rescan_bc
+rescan_spent
+save
+save_bc
+save_watch_only
+seed
+set
+set_tx_note
+show_transfers
+sign
+spendkey
+start_mining
+status
+stop_mining
+sweep_all
+sweep_unmixable
+transfer
+transfer_original
+verify
+viewkey
diff --git a/src/blockchain_db/lmdb/db_lmdb.cpp b/src/blockchain_db/lmdb/db_lmdb.cpp
index 8ad875fc8..acb7d2cf6 100644
--- a/src/blockchain_db/lmdb/db_lmdb.cpp
+++ b/src/blockchain_db/lmdb/db_lmdb.cpp
@@ -1081,7 +1081,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
if (need_resize())
{
- LOG_PRINT_L0("LMDB memory map needs resized, doing that now.");
+ LOG_PRINT_L0("LMDB memory map needs to be resized, doing that now.");
do_resize();
}
@@ -1132,7 +1132,7 @@ void BlockchainLMDB::open(const std::string& filename, const int mdb_flags)
if (!(mdb_flags & MDB_RDONLY))
{
result = mdb_drop(txn, m_hf_starting_heights, 1);
- if (result)
+ if (result && result != MDB_NOTFOUND)
throw0(DB_ERROR(lmdb_error("Failed to drop m_hf_starting_heights: ", result).c_str()));
}
@@ -2500,7 +2500,7 @@ uint64_t BlockchainLMDB::add_block(const block& blk, const size_t& block_size, c
// for batch mode, DB resize check is done at start of batch transaction
if (! m_batch_active && need_resize())
{
- LOG_PRINT_L0("LMDB memory map needs resized, doing that now.");
+ LOG_PRINT_L0("LMDB memory map needs to be resized, doing that now.");
do_resize();
}
}
diff --git a/src/common/command_line.cpp b/src/common/command_line.cpp
index fc096abe5..b3f488447 100644
--- a/src/common/command_line.cpp
+++ b/src/common/command_line.cpp
@@ -30,6 +30,7 @@
#include "command_line.h"
#include "string_tools.h"
+#include "cryptonote_config.h"
namespace command_line
{
@@ -92,4 +93,9 @@ namespace command_line
, "Show time-stats when processing blocks/txs and disk synchronization."
, 0
};
+ const command_line::arg_descriptor<size_t> arg_block_sync_size = {
+ "block-sync-size"
+ , "How many blocks to sync at once during chain synchronization."
+ , BLOCKS_SYNCHRONIZING_DEFAULT_COUNT
+ };
}
diff --git a/src/common/command_line.h b/src/common/command_line.h
index 731b8b0bb..0ea749168 100644
--- a/src/common/command_line.h
+++ b/src/common/command_line.h
@@ -216,4 +216,5 @@ namespace command_line
extern const arg_descriptor<uint64_t> arg_prep_blocks_threads;
extern const arg_descriptor<uint64_t> arg_db_auto_remove_logs;
extern const arg_descriptor<uint64_t> arg_show_time_stats;
+ extern const arg_descriptor<size_t> arg_block_sync_size;
}
diff --git a/src/cryptonote_core/cryptonote_core.cpp b/src/cryptonote_core/cryptonote_core.cpp
index c289f297b..4abf6a898 100644
--- a/src/cryptonote_core/cryptonote_core.cpp
+++ b/src/cryptonote_core/cryptonote_core.cpp
@@ -142,6 +142,7 @@ namespace cryptonote
command_line::add_arg(desc, command_line::arg_db_sync_mode);
command_line::add_arg(desc, command_line::arg_show_time_stats);
command_line::add_arg(desc, command_line::arg_db_auto_remove_logs);
+ command_line::add_arg(desc, command_line::arg_block_sync_size);
}
//-----------------------------------------------------------------------------------------------
bool core::handle_command_line(const boost::program_options::variables_map& vm)
@@ -403,6 +404,10 @@ namespace cryptonote
m_blockchain_storage.set_show_time_stats(show_time_stats);
CHECK_AND_ASSERT_MES(r, false, "Failed to initialize blockchain storage");
+ block_sync_size = command_line::get_arg(vm, command_line::arg_block_sync_size);
+ if (block_sync_size == 0)
+ block_sync_size = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT;
+
// load json & DNS checkpoints, and verify them
// with respect to what blocks we already have
CHECK_AND_ASSERT_MES(update_checkpoints(), false, "One or more checkpoints loaded from json or dns conflicted with existing checkpoints.");
diff --git a/src/cryptonote_core/cryptonote_core.h b/src/cryptonote_core/cryptonote_core.h
index d16bd6553..97abf3271 100644
--- a/src/cryptonote_core/cryptonote_core.h
+++ b/src/cryptonote_core/cryptonote_core.h
@@ -611,6 +611,13 @@ namespace cryptonote
*/
bool are_key_images_spent(const std::vector<crypto::key_image>& key_im, std::vector<bool> &spent) const;
+ /**
+ * @brief get the number of blocks to sync in one go
+ *
+ * @return the number of blocks to sync in one go
+ */
+ size_t get_block_sync_size() const { return block_sync_size; }
+
private:
/**
@@ -798,6 +805,8 @@ namespace cryptonote
std::atomic_flag m_checkpoints_updating; //!< set if checkpoints are currently updating to avoid multiple threads attempting to update at once
boost::interprocess::file_lock db_lock; //!< a lock object for a file lock in the db directory
+
+ size_t block_sync_size;
};
}
diff --git a/src/cryptonote_protocol/cryptonote_protocol_handler.inl b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
index f16dad281..6dfc9fbc5 100644
--- a/src/cryptonote_protocol/cryptonote_protocol_handler.inl
+++ b/src/cryptonote_protocol/cryptonote_protocol_handler.inl
@@ -650,9 +650,9 @@ namespace cryptonote
size_t count = 0;
auto it = context.m_needed_objects.begin();
- size_t count_limit = BLOCKS_SYNCHRONIZING_DEFAULT_COUNT;
+ const size_t count_limit = m_core.get_block_sync_size();
_note_c("net/req-calc" , "Setting count_limit: " << count_limit);
- while(it != context.m_needed_objects.end() && count < BLOCKS_SYNCHRONIZING_DEFAULT_COUNT)
+ while(it != context.m_needed_objects.end() && count < count_limit)
{
if( !(check_having_blocks && m_core.have_block(*it)))
{
diff --git a/src/simplewallet/simplewallet.cpp b/src/simplewallet/simplewallet.cpp
index 7260ecf8a..279a5fa41 100644
--- a/src/simplewallet/simplewallet.cpp
+++ b/src/simplewallet/simplewallet.cpp
@@ -662,7 +662,7 @@ simple_wallet::simple_wallet()
m_cmd_binder.set_handler("viewkey", boost::bind(&simple_wallet::viewkey, this, _1), tr("Display private view key"));
m_cmd_binder.set_handler("spendkey", boost::bind(&simple_wallet::spendkey, this, _1), tr("Display private spend key"));
m_cmd_binder.set_handler("seed", boost::bind(&simple_wallet::seed, this, _1), tr("Display Electrum-style mnemonic seed"));
- m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee"));
+ m_cmd_binder.set_handler("set", boost::bind(&simple_wallet::set_variable, this, _1), tr("Available options: seed language - set wallet seed language; always-confirm-transfers <1|0> - whether to confirm unsplit txes; store-tx-info <1|0> - whether to store outgoing tx info (destination address, payment ID, tx secret key) for future reference; default-mixin <n> - set default mixin (default is 4); auto-refresh <1|0> - whether to automatically sync new blocks from the daemon; refresh-type <full|optimize-coinbase|no-coinbase|default> - set wallet refresh behaviour; priority [1|2|3] - normal/elevated/priority fee"));
m_cmd_binder.set_handler("rescan_spent", boost::bind(&simple_wallet::rescan_spent, this, _1), tr("Rescan blockchain for spent outputs"));
m_cmd_binder.set_handler("get_tx_key", boost::bind(&simple_wallet::get_tx_key, this, _1), tr("Get transaction key (r) for a given <txid>"));
m_cmd_binder.set_handler("check_tx_key", boost::bind(&simple_wallet::check_tx_key, this, _1), tr("Check amount going to <address> in <txid>"));
@@ -2489,7 +2489,7 @@ bool simple_wallet::transfer_main(int transfer_type, const std::vector<std::stri
print_money(e.tx_amount() + e.fee()) %
print_money(e.tx_amount()) %
print_money(e.fee()));
- fail_msg_writer() << tr("Failed to find a way to create transactions");
+ fail_msg_writer() << tr("Failed to find a way to create transactions. This is usually due to dust which is so small it cannot pay for itself in fees");
}
catch (const tools::error::not_enough_outs_to_mix& e)
{
diff --git a/src/wallet/api/wallet.cpp b/src/wallet/api/wallet.cpp
index 74552bc03..eefb49e95 100644
--- a/src/wallet/api/wallet.cpp
+++ b/src/wallet/api/wallet.cpp
@@ -46,7 +46,9 @@ namespace Bitmonero {
namespace {
// copy-pasted from simplewallet
static const size_t DEFAULT_MIXIN = 4;
- static const int DEFAULT_REFRESH_INTERVAL_SECONDS = 10;
+ static const int DEFAULT_REFRESH_INTERVAL_MILLIS = 1000 * 10;
+ // limit maximum refresh interval as one minute
+ static const int MAX_REFRESH_INTERVAL_MILLIS = 1000 * 60 * 1;
}
struct Wallet2CallbackImpl : public tools::i_wallet2_callback
@@ -75,8 +77,12 @@ struct Wallet2CallbackImpl : public tools::i_wallet2_callback
virtual void on_new_block(uint64_t height, const cryptonote::block& block)
{
- // TODO;
LOG_PRINT_L3(__FUNCTION__ << ": new block. height: " << height);
+
+ if (m_listener) {
+ m_listener->newBlock(height);
+ // m_listener->updated();
+ }
}
virtual void on_money_received(uint64_t height, const cryptonote::transaction& tx, uint64_t amount)
@@ -169,7 +175,9 @@ WalletImpl::WalletImpl(bool testnet)
m_wallet->callback(m_wallet2Callback);
m_refreshThreadDone = false;
m_refreshEnabled = false;
- m_refreshIntervalSeconds = DEFAULT_REFRESH_INTERVAL_SECONDS;
+
+ m_refreshIntervalMillis = DEFAULT_REFRESH_INTERVAL_MILLIS;
+
m_refreshThread = boost::thread([this] () {
this->refreshThreadFunc();
});
@@ -272,14 +280,15 @@ bool WalletImpl::close()
{
bool result = false;
+ LOG_PRINT_L3("closing wallet...");
try {
// do not store wallet with invalid status
if (status() == Status_Ok)
m_wallet->store();
- // LOG_PRINT_L0("wallet::store done");
- // LOG_PRINT_L0("Calling wallet::stop...");
+ LOG_PRINT_L3("wallet::store done");
+ LOG_PRINT_L3("Calling wallet::stop...");
m_wallet->stop();
- // LOG_PRINT_L0("wallet::stop done");
+ LOG_PRINT_L3("wallet::stop done");
result = true;
clearStatus();
} catch (const std::exception &e) {
@@ -410,6 +419,27 @@ uint64_t WalletImpl::unlockedBalance() const
return m_wallet->unlocked_balance();
}
+uint64_t WalletImpl::blockChainHeight() const
+{
+ return m_wallet->get_blockchain_current_height();
+}
+
+uint64_t WalletImpl::daemonBlockChainHeight() const
+{
+ std::string err;
+ uint64_t result = m_wallet->get_daemon_blockchain_height(err);
+ if (!err.empty()) {
+ LOG_ERROR(__FUNCTION__ << ": " << err);
+ result = 0;
+ m_errorString = err;
+ m_status = Status_Error;
+
+ } else {
+ m_status = Status_Ok;
+ m_errorString = "";
+ }
+ return result;
+}
bool WalletImpl::refresh()
{
@@ -425,6 +455,22 @@ void WalletImpl::refreshAsync()
m_refreshCV.notify_one();
}
+void WalletImpl::setAutoRefreshInterval(int millis)
+{
+ if (millis > MAX_REFRESH_INTERVAL_MILLIS) {
+ LOG_ERROR(__FUNCTION__<< ": invalid refresh interval " << millis
+ << " ms, maximum allowed is " << MAX_REFRESH_INTERVAL_MILLIS << " ms");
+ m_refreshIntervalMillis = MAX_REFRESH_INTERVAL_MILLIS;
+ } else {
+ m_refreshIntervalMillis = millis;
+ }
+}
+
+int WalletImpl::autoRefreshInterval() const
+{
+ return m_refreshIntervalMillis;
+}
+
// TODO:
// 1 - properly handle payment id (add another menthod with explicit 'payment_id' param)
// 2 - check / design how "Transaction" can be single interface
@@ -637,7 +683,15 @@ void WalletImpl::refreshThreadFunc()
break;
}
LOG_PRINT_L3(__FUNCTION__ << ": waiting for refresh...");
- m_refreshCV.wait(lock);
+ // if auto refresh enabled, we wait for the "m_refreshIntervalSeconds" interval.
+ // if not - we wait forever
+ if (m_refreshIntervalMillis > 0) {
+ boost::posix_time::milliseconds wait_for_ms(m_refreshIntervalMillis);
+ m_refreshCV.timed_wait(lock, wait_for_ms);
+ } else {
+ m_refreshCV.wait(lock);
+ }
+
LOG_PRINT_L3(__FUNCTION__ << ": refresh lock acquired...");
LOG_PRINT_L3(__FUNCTION__ << ": m_refreshEnabled: " << m_refreshEnabled);
LOG_PRINT_L3(__FUNCTION__ << ": m_status: " << m_status);
@@ -680,6 +734,7 @@ void WalletImpl::stopRefresh()
if (!m_refreshThreadDone) {
m_refreshEnabled = false;
m_refreshThreadDone = true;
+ m_refreshCV.notify_one();
m_refreshThread.join();
}
}
diff --git a/src/wallet/api/wallet.h b/src/wallet/api/wallet.h
index 658296c30..d97a8f3b3 100644
--- a/src/wallet/api/wallet.h
+++ b/src/wallet/api/wallet.h
@@ -75,8 +75,15 @@ public:
bool trustedDaemon() const;
uint64_t balance() const;
uint64_t unlockedBalance() const;
+ uint64_t blockChainHeight() const;
+ uint64_t daemonBlockChainHeight() const;
bool refresh();
void refreshAsync();
+ void setAutoRefreshInterval(int millis);
+ int autoRefreshInterval() const;
+
+
+
PendingTransaction * createTransaction(const std::string &dst_addr, const std::string &payment_id,
uint64_t amount, uint32_t mixin_count,
PendingTransaction::Priority priority = PendingTransaction::Priority_Low);
@@ -100,8 +107,8 @@ private:
friend class TransactionHistoryImpl;
tools::wallet2 * m_wallet;
- std::atomic<int> m_status;
- std::string m_errorString;
+ mutable std::atomic<int> m_status;
+ mutable std::string m_errorString;
std::string m_password;
TransactionHistoryImpl * m_history;
bool m_trustedDaemon;
@@ -111,7 +118,7 @@ private:
// multi-threaded refresh stuff
std::atomic<bool> m_refreshEnabled;
std::atomic<bool> m_refreshThreadDone;
- std::atomic<int> m_refreshIntervalSeconds;
+ std::atomic<int> m_refreshIntervalMillis;
// synchronizing refresh loop;
boost::mutex m_refreshMutex;
diff --git a/src/wallet/wallet2.cpp b/src/wallet/wallet2.cpp
index 3d4f93aff..ed4ab93de 100644
--- a/src/wallet/wallet2.cpp
+++ b/src/wallet/wallet2.cpp
@@ -4053,6 +4053,38 @@ std::string wallet2::get_daemon_address() const
return m_daemon_address;
}
+uint64_t wallet2::get_daemon_blockchain_height(string &err)
+{
+ // XXX: DRY violation. copy-pasted from simplewallet.cpp:get_daemon_blockchain_height()
+ // consider to move it from simplewallet to wallet2 ?
+ COMMAND_RPC_GET_HEIGHT::request req;
+ COMMAND_RPC_GET_HEIGHT::response res = boost::value_initialized<COMMAND_RPC_GET_HEIGHT::response>();
+ m_daemon_rpc_mutex.lock();
+ bool ok = net_utils::invoke_http_json_remote_command2(m_daemon_address + "/getheight", req, res, m_http_client);
+ m_daemon_rpc_mutex.unlock();
+ // XXX: DRY violation. copy-pasted from simplewallet.cpp:interpret_rpc_response()
+ if (ok)
+ {
+ if (res.status == CORE_RPC_STATUS_BUSY)
+ {
+ err = "daemon is busy. Please try again later.";
+ }
+ else if (res.status != CORE_RPC_STATUS_OK)
+ {
+ err = res.status;
+ }
+ else // success, cleaning up error message
+ {
+ err = "";
+ }
+ }
+ else
+ {
+ err = "possibly lost connection to daemon";
+ }
+ return res.height;
+}
+
void wallet2::set_tx_note(const crypto::hash &txid, const std::string &note)
{
m_tx_notes[txid] = note;
diff --git a/src/wallet/wallet2.h b/src/wallet/wallet2.h
index 89b613d34..dd7cd19dc 100644
--- a/src/wallet/wallet2.h
+++ b/src/wallet/wallet2.h
@@ -400,6 +400,7 @@ namespace tools
std::string get_wallet_file() const;
std::string get_keys_file() const;
std::string get_daemon_address() const;
+ uint64_t get_daemon_blockchain_height(std::string& err);
std::vector<size_t> select_available_outputs_from_histogram(uint64_t count, bool atleast, bool unlocked, bool trusted_daemon);
std::vector<size_t> select_available_outputs(const std::function<bool(const transfer_details &td)> &f);
diff --git a/src/wallet/wallet2_api.h b/src/wallet/wallet2_api.h
index e880b1c68..08e2ae16b 100644
--- a/src/wallet/wallet2_api.h
+++ b/src/wallet/wallet2_api.h
@@ -114,11 +114,35 @@ struct TransactionHistory
struct WalletListener
{
virtual ~WalletListener() = 0;
+ /**
+ * @brief moneySpent - called when money spent
+ * @param txId - transaction id
+ * @param amount - amount
+ */
virtual void moneySpent(const std::string &txId, uint64_t amount) = 0;
+
+ /**
+ * @brief moneyReceived - called when money received
+ * @param txId - transaction id
+ * @param amount - amount
+ */
virtual void moneyReceived(const std::string &txId, uint64_t amount) = 0;
- // generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
+
+ /**
+ * @brief newBlock - called when new block received
+ * @param height - block height
+ */
+ virtual void newBlock(uint64_t height) = 0;
+
+ /**
+ * @brief updated - generic callback, called when any event (sent/received/block reveived/etc) happened with the wallet;
+ */
virtual void updated() = 0;
- // called when wallet refreshed by background thread or explicitly called be calling "refresh" synchronously
+
+
+ /**
+ * @brief refreshed - called when wallet refreshed by background thread or explicitly refreshed by calling "refresh" synchronously
+ */
virtual void refreshed() = 0;
};
@@ -211,6 +235,20 @@ struct Wallet
virtual uint64_t balance() const = 0;
virtual uint64_t unlockedBalance() const = 0;
+ /**
+ * @brief blockChainHeight - returns current blockchain height
+ * @return
+ */
+ virtual uint64_t blockChainHeight() const = 0;
+
+ /**
+ * @brief daemonBlockChainHeight - returns daemon blockchain height
+ * @return 0 - in case error communicating with the daemon.
+ * status() will return Status_Error and errorString() will return verbose error description
+ */
+ virtual uint64_t daemonBlockChainHeight() const = 0;
+
+
static std::string displayAmount(uint64_t amount);
static uint64_t amountFromString(const std::string &amount);
static uint64_t amountFromDouble(double amount);
@@ -223,10 +261,25 @@ struct Wallet
* @return - true if refreshed successfully;
*/
virtual bool refresh() = 0;
+
/**
* @brief refreshAsync - refreshes wallet asynchronously.
*/
virtual void refreshAsync() = 0;
+
+ /**
+ * @brief setAutoRefreshInterval - setup interval for automatic refresh.
+ * @param seconds - interval in millis. if zero or less than zero - automatic refresh disabled;
+ */
+ virtual void setAutoRefreshInterval(int millis) = 0;
+
+ /**
+ * @brief autoRefreshInterval - returns automatic refresh interval in millis
+ * @return
+ */
+ virtual int autoRefreshInterval() const = 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/tests/libwallet_api_tests/main.cpp b/tests/libwallet_api_tests/main.cpp
index b4bc86f91..87e0cc935 100644
--- a/tests/libwallet_api_tests/main.cpp
+++ b/tests/libwallet_api_tests/main.cpp
@@ -36,6 +36,8 @@
#include <boost/filesystem.hpp>
#include <boost/algorithm/string.hpp>
+#include <boost/asio.hpp>
+#include <boost/date_time/posix_time/posix_time.hpp>
#include <iostream>
#include <vector>
@@ -63,29 +65,28 @@ const char * WALLET_PASS = "password";
const char * WALLET_PASS2 = "password22";
const char * WALLET_LANG = "English";
-// change this according your environment
-
-const std::string WALLETS_ROOT_DIR = "/home/mbg033/dev/monero/testnet/";
-
-const std::string TESTNET_WALLET1_NAME = WALLETS_ROOT_DIR + "wallet_01.bin";
-const std::string TESTNET_WALLET2_NAME = WALLETS_ROOT_DIR + "wallet_02.bin";
-const std::string TESTNET_WALLET3_NAME = WALLETS_ROOT_DIR + "wallet_03.bin";
-const std::string TESTNET_WALLET4_NAME = WALLETS_ROOT_DIR + "wallet_04.bin";
-const std::string TESTNET_WALLET5_NAME = WALLETS_ROOT_DIR + "wallet_05.bin";
-const std::string TESTNET_WALLET6_NAME = WALLETS_ROOT_DIR + "wallet_06.bin";
+std::string WALLETS_ROOT_DIR = "/var/monero/testnet_pvt";
+std::string TESTNET_WALLET1_NAME;
+std::string TESTNET_WALLET2_NAME;
+std::string TESTNET_WALLET3_NAME;
+std::string TESTNET_WALLET4_NAME;
+std::string TESTNET_WALLET5_NAME;
+std::string TESTNET_WALLET6_NAME;
const char * TESTNET_WALLET_PASS = "";
-const std::string CURRENT_SRC_WALLET = TESTNET_WALLET1_NAME;
-const std::string CURRENT_DST_WALLET = TESTNET_WALLET6_NAME;
+std::string CURRENT_SRC_WALLET;
+std::string CURRENT_DST_WALLET;
-const char * TESTNET_DAEMON_ADDRESS = "localhost:38081";
const uint64_t AMOUNT_10XMR = 10000000000000L;
const uint64_t AMOUNT_5XMR = 5000000000000L;
const uint64_t AMOUNT_1XMR = 1000000000000L;
const std::string PAYMENT_ID_EMPTY = "";
+std::string TESTNET_DAEMON_ADDRESS = "localhost:38081";
+
+
}
@@ -179,10 +180,8 @@ struct WalletTest2 : public testing::Test
wmgr = Bitmonero::WalletManagerFactory::getWalletManager();
}
-
};
-
TEST_F(WalletManagerTest, WalletManagerCreatesWallet)
{
@@ -220,6 +219,7 @@ TEST_F(WalletManagerTest, WalletMaxAmountAsString)
}
+
TEST_F(WalletManagerTest, WalletAmountFromString)
{
uint64_t amount = Bitmonero::Wallet::amountFromString("18446740");
@@ -394,6 +394,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet2)
ASSERT_TRUE(wmgr->closeWallet(wallet1));
}
+
TEST_F(WalletManagerTest, WalletManagerStoresWallet3)
{
Bitmonero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG);
@@ -406,7 +407,8 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet3)
wallet1 = wmgr->openWallet(WALLET_NAME_WITH_DIR_NON_WRITABLE, WALLET_PASS);
ASSERT_FALSE(wallet1->status() == Bitmonero::Wallet::Status_Ok);
- ASSERT_FALSE(wmgr->closeWallet(wallet1));
+ // "close" always returns true;
+ ASSERT_TRUE(wmgr->closeWallet(wallet1));
wallet1 = wmgr->openWallet(WALLET_NAME, WALLET_PASS);
ASSERT_TRUE(wallet1->status() == Bitmonero::Wallet::Status_Ok);
@@ -416,6 +418,7 @@ TEST_F(WalletManagerTest, WalletManagerStoresWallet3)
}
+
TEST_F(WalletManagerTest, WalletManagerStoresWallet4)
{
Bitmonero::Wallet * wallet1 = wmgr->createWallet(WALLET_NAME, WALLET_PASS, WALLET_LANG);
@@ -451,14 +454,14 @@ TEST_F(WalletManagerTest, WalletManagerFindsWallet)
}
-TEST_F(WalletManagerTest, WalletGeneratesPaymentId)
+TEST_F(WalletTest1, WalletGeneratesPaymentId)
{
std::string payment_id = Bitmonero::Wallet::genPaymentId();
ASSERT_TRUE(payment_id.length() == 16);
}
-TEST_F(WalletManagerTest, WalletGeneratesIntegratedAddress)
+TEST_F(WalletTest1, WalletGeneratesIntegratedAddress)
{
std::string payment_id = Bitmonero::Wallet::genPaymentId();
@@ -470,8 +473,6 @@ TEST_F(WalletManagerTest, WalletGeneratesIntegratedAddress)
TEST_F(WalletTest1, WalletShowsBalance)
{
- // TODO: temporary disabled;
- return;
Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
ASSERT_TRUE(wallet1->balance() > 0);
ASSERT_TRUE(wallet1->unlockedBalance() > 0);
@@ -488,8 +489,35 @@ TEST_F(WalletTest1, WalletShowsBalance)
ASSERT_TRUE(wmgr->closeWallet(wallet2));
}
+TEST_F(WalletTest1, WalletReturnsCurrentBlockHeight)
+{
+ Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
+ ASSERT_TRUE(wallet1->blockChainHeight() > 0);
+ wmgr->closeWallet(wallet1);
+}
+
+
+TEST_F(WalletTest1, WalletReturnsDaemonBlockHeight)
+{
+ Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
+ // wallet not connected to daemon
+ ASSERT_TRUE(wallet1->daemonBlockChainHeight() == 0);
+ ASSERT_TRUE(wallet1->status() != Bitmonero::Wallet::Status_Ok);
+ ASSERT_FALSE(wallet1->errorString().empty());
+ wmgr->closeWallet(wallet1);
+
+ wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
+ // wallet connected to daemon
+ wallet1->init(TESTNET_DAEMON_ADDRESS, 0);
+ ASSERT_TRUE(wallet1->daemonBlockChainHeight() > 0);
+ std::cout << "daemonBlockChainHeight: " << wallet1->daemonBlockChainHeight() << std::endl;
+ wmgr->closeWallet(wallet1);
+}
+
+
TEST_F(WalletTest1, WalletRefresh)
{
+
Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
// make sure testnet daemon is running
ASSERT_TRUE(wallet1->init(TESTNET_DAEMON_ADDRESS, 0));
@@ -497,7 +525,6 @@ TEST_F(WalletTest1, WalletRefresh)
ASSERT_TRUE(wmgr->closeWallet(wallet1));
}
-
TEST_F(WalletTest1, WalletConvertsToString)
{
std::string strAmount = Bitmonero::Wallet::displayAmount(AMOUNT_5XMR);
@@ -512,6 +539,7 @@ TEST_F(WalletTest1, WalletConvertsToString)
TEST_F(WalletTest1, WalletTransaction)
+
{
Bitmonero::Wallet * wallet1 = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
// make sure testnet daemon is running
@@ -538,6 +566,8 @@ TEST_F(WalletTest1, WalletTransaction)
ASSERT_TRUE(wmgr->closeWallet(wallet1));
}
+
+
TEST_F(WalletTest1, WalletTransactionWithMixin)
{
@@ -736,8 +766,10 @@ struct MyWalletListener : public Bitmonero::WalletListener
std::condition_variable cv_receive;
std::condition_variable cv_update;
std::condition_variable cv_refresh;
+ std::condition_variable cv_newblock;
bool send_triggered;
bool receive_triggered;
+ bool newblock_triggered;
bool update_triggered;
bool refresh_triggered;
@@ -775,6 +807,14 @@ struct MyWalletListener : public Bitmonero::WalletListener
cv_receive.notify_one();
}
+ virtual void newBlock(uint64_t height)
+ {
+ std::cout << "wallet: " << wallet->address()
+ <<", new block received, blockHeight: " << height << std::endl;
+ newblock_triggered = true;
+ cv_newblock.notify_one();
+ }
+
virtual void updated()
{
std::cout << __FUNCTION__ << "Wallet updated";
@@ -792,6 +832,8 @@ struct MyWalletListener : public Bitmonero::WalletListener
};
+
+
TEST_F(WalletTest2, WalletCallBackRefreshedSync)
{
@@ -800,13 +842,15 @@ TEST_F(WalletTest2, WalletCallBackRefreshedSync)
ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0));
ASSERT_TRUE(wallet_src_listener->refresh_triggered);
ASSERT_TRUE(wallet_src->connected());
-// std::chrono::seconds wait_for = std::chrono::seconds(60*3);
-// std::unique_lock<std::mutex> lock (wallet_src_listener->mutex);
-// wallet_src_listener->cv_refresh.wait_for(lock, wait_for);
+ std::chrono::seconds wait_for = std::chrono::seconds(60*3);
+ std::unique_lock<std::mutex> lock (wallet_src_listener->mutex);
+ wallet_src_listener->cv_refresh.wait_for(lock, wait_for);
wmgr->closeWallet(wallet_src);
}
+
+
TEST_F(WalletTest2, WalletCallBackRefreshedAsync)
{
@@ -869,16 +913,17 @@ TEST_F(WalletTest2, WalletCallbackSent)
TEST_F(WalletTest2, WalletCallbackReceived)
{
- Bitmonero::Wallet * wallet_src = wmgr->openWallet(TESTNET_WALLET5_NAME, TESTNET_WALLET_PASS, true);
+ Bitmonero::Wallet * wallet_src = wmgr->openWallet(CURRENT_SRC_WALLET, TESTNET_WALLET_PASS, true);
// make sure testnet daemon is running
ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0));
ASSERT_TRUE(wallet_src->refresh());
- std::cout << "** Balance: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl;
+ std::cout << "** Balance src1: " << wallet_src->displayAmount(wallet_src->balance()) << std::endl;
Bitmonero::Wallet * wallet_dst = wmgr->openWallet(CURRENT_DST_WALLET, TESTNET_WALLET_PASS, true);
ASSERT_TRUE(wallet_dst->init(TESTNET_DAEMON_ADDRESS, 0));
ASSERT_TRUE(wallet_dst->refresh());
uint64_t balance = wallet_dst->balance();
+ std::cout << "** Balance dst1: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl;
MyWalletListener * wallet_dst_listener = new MyWalletListener(wallet_dst);
uint64_t amount = AMOUNT_1XMR * 5;
@@ -900,7 +945,10 @@ TEST_F(WalletTest2, WalletCallbackReceived)
std::cerr << "TEST: receive lock acquired...\n";
ASSERT_TRUE(wallet_dst_listener->receive_triggered);
ASSERT_TRUE(wallet_dst_listener->update_triggered);
- std::cout << "** Balance: " << wallet_dst->displayAmount(wallet_src->balance()) << std::endl;
+
+ std::cout << "** Balance src2: " << wallet_dst->displayAmount(wallet_src->balance()) << std::endl;
+ std::cout << "** Balance dst2: " << wallet_dst->displayAmount(wallet_dst->balance()) << std::endl;
+
ASSERT_TRUE(wallet_dst->balance() > balance);
wmgr->closeWallet(wallet_src);
@@ -909,9 +957,61 @@ TEST_F(WalletTest2, WalletCallbackReceived)
+TEST_F(WalletTest2, WalletCallbackNewBlock)
+{
+
+ Bitmonero::Wallet * wallet_src = wmgr->openWallet(TESTNET_WALLET5_NAME, TESTNET_WALLET_PASS, true);
+ // make sure testnet daemon is running
+ ASSERT_TRUE(wallet_src->init(TESTNET_DAEMON_ADDRESS, 0));
+ ASSERT_TRUE(wallet_src->refresh());
+ uint64_t bc1 = wallet_src->blockChainHeight();
+ std::cout << "** Block height: " << bc1 << std::endl;
+
+
+ MyWalletListener * wallet_listener = new MyWalletListener(wallet_src);
+
+ // wait max 4 min for new block
+ std::chrono::seconds wait_for = std::chrono::seconds(60*4);
+ std::unique_lock<std::mutex> lock (wallet_listener->mutex);
+ std::cerr << "TEST: waiting on newblock lock...\n";
+ wallet_listener->cv_newblock.wait_for(lock, wait_for);
+ std::cerr << "TEST: newblock lock acquired...\n";
+ ASSERT_TRUE(wallet_listener->newblock_triggered);
+ uint64_t bc2 = wallet_src->blockChainHeight();
+ std::cout << "** Block height: " << bc2 << std::endl;
+ ASSERT_TRUE(bc2 > bc1);
+ wmgr->closeWallet(wallet_src);
+
+}
+
+
+
int main(int argc, char** argv)
{
+ // we can override default values for "TESTNET_DAEMON_ADDRESS" and "WALLETS_ROOT_DIR"
+
+ const char * monero_daemon_addr = std::getenv("TESTNET_DAEMON_ADDRESS");
+ if (monero_daemon_addr) {
+ TESTNET_DAEMON_ADDRESS = monero_daemon_addr;
+ }
+
+ const char * wallets_root_dir = std::getenv("WALLETS_ROOT_DIR");
+ if (wallets_root_dir) {
+ WALLETS_ROOT_DIR = wallets_root_dir;
+ }
+
+
+ TESTNET_WALLET1_NAME = WALLETS_ROOT_DIR + "/wallet_01.bin";
+ TESTNET_WALLET2_NAME = WALLETS_ROOT_DIR + "/wallet_02.bin";
+ TESTNET_WALLET3_NAME = WALLETS_ROOT_DIR + "/wallet_03.bin";
+ TESTNET_WALLET4_NAME = WALLETS_ROOT_DIR + "/wallet_04.bin";
+ TESTNET_WALLET5_NAME = WALLETS_ROOT_DIR + "/wallet_05.bin";
+ TESTNET_WALLET6_NAME = WALLETS_ROOT_DIR + "/wallet_06.bin";
+
+ CURRENT_SRC_WALLET = TESTNET_WALLET6_NAME;
+ CURRENT_DST_WALLET = TESTNET_WALLET5_NAME;
::testing::InitGoogleTest(&argc, argv);
+ // Bitmonero::WalletManagerFactory::setLogLevel(Bitmonero::WalletManagerFactory::LogLevel_Max);
return RUN_ALL_TESTS();
}
diff --git a/tests/libwallet_api_tests/scripts/README.md b/tests/libwallet_api_tests/scripts/README.md
new file mode 100644
index 000000000..2705cc04b
--- /dev/null
+++ b/tests/libwallet_api_tests/scripts/README.md
@@ -0,0 +1,24 @@
+# Running libwallet_api tests
+
+## Environment for the tests
+* Running monero node, linked to private/public testnet.
+ By default, tests expect daemon running at ```localhost:38081```,
+ can we overriden with enviroment variable ```TESTNET_DAEMON_ADDRESS=<your_daemon_address>```
+ [Manual](https://github.com/moneroexamples/private-testnet) explaining how to run private testnet.
+
+* Directory with pre-generated wallets
+ (wallet_01.bin, wallet_02.bin,...,wallet_06.bin, some of these wallets might not be used in the tests currently).
+ By default, tests expect these wallets to be in ```/var/monero/testnet_pvt```.
+ Directory can be overriden with environment variable ```WALLETS_ROOT_DIR=<your_directory_with_wallets>```.
+ Directory and files should be writable for the user running tests.
+
+
+## Generating test wallets
+* ```create_wallets.sh``` - this script will create wallets (wallet_01.bin, wallet_02.bin,...,wallet_06.bin) in current directory.
+ when running first time, please uncomment line ```#create_wallet wallet_m``` to create miner wallet as well.
+ This wallet should be used for mining and all test wallets supposed to be seed from this miner wallet
+
+* ```mining_start.sh``` and ```mining_stop.sh``` - helper scripts to start and stop mining on miner waller
+
+* ```send_funds.sh``` - script for seeding test wallets. Please run this script when you have ehough money on miner wallet
+
diff --git a/tests/libwallet_api_tests/scripts/create_wallets.sh b/tests/libwallet_api_tests/scripts/create_wallets.sh
index e25d2c317..f33564e7f 100755
--- a/tests/libwallet_api_tests/scripts/create_wallets.sh
+++ b/tests/libwallet_api_tests/scripts/create_wallets.sh
@@ -6,7 +6,6 @@ function create_wallet {
}
-
create_wallet wallet_01.bin
create_wallet wallet_02.bin
create_wallet wallet_03.bin
@@ -14,7 +13,6 @@ create_wallet wallet_04.bin
create_wallet wallet_05.bin
create_wallet wallet_06.bin
-
-#create_wallet wallet_m
+# create_wallet wallet_m
diff --git a/tests/libwallet_api_tests/scripts/send_funds.sh b/tests/libwallet_api_tests/scripts/send_funds.sh
index b7f282b71..3ce923353 100755
--- a/tests/libwallet_api_tests/scripts/send_funds.sh
+++ b/tests/libwallet_api_tests/scripts/send_funds.sh
@@ -1,7 +1,5 @@
#!/bin/bash
-
-
function send_funds {
local amount=$1
local dest=$(cat "$2.address.txt")
diff --git a/utils/gpg_keys/luigi1111.asc b/utils/gpg_keys/luigi1111.asc
new file mode 100644
index 000000000..4ac3de627
--- /dev/null
+++ b/utils/gpg_keys/luigi1111.asc
@@ -0,0 +1,30 @@
+-----BEGIN PGP PUBLIC KEY BLOCK-----
+Version: GnuPG v1
+
+mQENBFdodc4BCAC02POyrMUol+nTXeZ2nCKM9G1Q2oU5jQLaQLNLUU88PHLdOVGd
+nKh0QA4Uc4CA2wWU5zdIVFEzowUUNyKeSLJZhvOXbpN+pm6n9XiyNSZSDJhvMtAs
+FEcyWyAjPAQT7MFS2SJgG7HhcoMAgr3ypp8FpAK2YwbfjfPqs5VFVdrYrVPfiSVJ
+NTSoJMW+2oOUdTR/L6z8lzUZu0CH8aJK1Qqr0yyxbJelayPsos3aRQ9TQAu6u/zQ
+PxURHaLTe8RsB6oa6wXnpZtXcl1xaigvlkNVpeHPbroBvhJoCbAFHC/edaBNBLiP
+I3WCRGiCvvP2kIfuZlyv48gFX+E3NWSB10Q1ABEBAAG0IGx1aWdpMTExMSA8bHVp
+Z2kxMTExd0BnbWFpbC5jb20+iQE4BBMBAgAiBQJXaHXOAhsDBgsJCAcDAgYVCAIJ
+CgsEFgIDAQIeAQIXgAAKCRD0rKAYNkHgEAyoB/9agBv/xdNUgTVheW/O6kWG4DYG
+/1mMfuTF1xi8CSFbUID3dRhnjXtncuSCaY7030CSLtnC/Pl8MnKHmcBZcoCiARzI
+bNFK4dEdJjDU8yiqD+M0IAe58xCYQ7I/RX5TXJJ+WTS3lwi1zBqf9D4XVTQxSm+u
+3tVH5mUu35pRWVCg1OSwWo1IGkSDkeGq8ySdYUJdM535caKf8L1ICNir1H7pWNCa
+Jya/b3PRtSefw4tNHcsFtndl4S52iMdXwNUdwRKTP/hDa9KYxQqTGDtJ6nKjTy6p
+dgZxAiF2mL9dVYg6c+GHlrtZMUZd5NQZcIwu9jXzAiQKZMmtL6/RauzXRuz9uQEN
+BFdodc4BCADACy78WJBKCR4MrUcy4L0G4P5n+cqao2Lf+oc53xJudhcgufowsvzs
+nixKG9HC+6SoZCZwKYfglu0JxjvqpZC5U9nCwIWuf52Qu4pDO4xYTeuhVr+Lcjvg
+tqtJf5M9QaVJwAz8jTKaEAWUSXuKnljVC76zU8LTprYrTEdOyHO9E6Z94MrBUwS3
+6IVNlJ+q5wZyoj9noWbm7X0SIRER9/shr/UlfkSx0Kpnyj3ludkdg7TQR7jp9rGy
+GqRmOKH3eGv2aFO1fo+RDacn+R9Fh1vHIdUX6FZil+yft9lXg5jtxcNXvhIJtsoC
+NuVIWEYaQuzSFqtblTrM6IwJ8HrimEBPABEBAAGJAR8EGAECAAkFAldodc4CGwwA
+CgkQ9KygGDZB4BBnEAf+OSvWLNJs0VtKRIO5oHP7Ia6oS8v0IyXFLx2/p/JW7jMR
+3LVor2wvao52omW+JzoIj4J8cnaEEt2ptHpmzZApSQydZhHO+108hPDYbeWPrXa8
+aSXCtI++s2occAemsMvgrnzI7yuMFaAwOWOYL5bmR5pJnnuQQUj1vfXkqBEYYFyF
+u7dvJ3hLHLbxBFvIbDBMfzev+g6wOGkhT2/7osUACoLwTwzkbXZNSoQHiZ72YA/P
+9f3SA5Zc5hGopft96sAAnzk6sm/xCfFO1uj6Yk/H/fd/ZjVzQW+XBUvwM/jIqpuX
+Ftwy4Ulp0YZT5BpXkHDhosL6o4GelJULMbxreh7nNQ==
+=j0V6
+-----END PGP PUBLIC KEY BLOCK----- \ No newline at end of file