diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index 8ec520c5..50705196 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -4150,3 +4150,67 @@ TEST (rpc, uptime) ASSERT_EQ (200, response.status); ASSERT_LE (1, response.json.get ("seconds")); } + +TEST (rpc, wallet_history) +{ + nano::system system (24000, 1); + auto node0 (system.nodes[0]); + nano::genesis genesis; + system.wallet (0)->insert_adhoc (nano::test_genesis_key.prv); + auto timestamp1 (nano::seconds_since_epoch ()); + auto send (system.wallet (0)->send_action (nano::test_genesis_key.pub, nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + std::this_thread::sleep_for (std::chrono::milliseconds (1000)); + auto timestamp2 (nano::seconds_since_epoch ()); + auto receive (system.wallet (0)->receive_action (static_cast (*send), nano::test_genesis_key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + nano::keypair key; + std::this_thread::sleep_for (std::chrono::milliseconds (1000)); + auto timestamp3 (nano::seconds_since_epoch ()); + auto send2 (system.wallet (0)->send_action (nano::test_genesis_key.pub, key.pub, node0->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send2); + system.deadline_set (10s); + nano::rpc rpc (system.io_ctx, *node0, nano::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "wallet_history"); + request.put ("wallet", node0->wallets.items.begin ()->first.to_string ()); + test_response response (request, rpc, system.io_ctx); + system.deadline_set (5s); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::vector> history_l; + auto & history_node (response.json.get_child ("history")); + for (auto i (history_node.begin ()), n (history_node.end ()); i != n; ++i) + { + history_l.push_back (std::make_tuple (i->second.get ("type"), i->second.get ("account"), i->second.get ("amount"), i->second.get ("hash"), i->second.get ("wallet_account"), i->second.get ("local_timestamp"))); + } + ASSERT_EQ (4, history_l.size ()); + ASSERT_EQ ("send", std::get<0> (history_l[0])); + ASSERT_EQ (key.pub.to_account (), std::get<1> (history_l[0])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[0])); + ASSERT_EQ (send2->hash ().to_string (), std::get<3> (history_l[0])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[0])); + ASSERT_EQ (std::to_string (timestamp3), std::get<5> (history_l[0])); + ASSERT_EQ ("receive", std::get<0> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[1])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[1])); + ASSERT_EQ (receive->hash ().to_string (), std::get<3> (history_l[1])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[1])); + ASSERT_EQ (std::to_string (timestamp2), std::get<5> (history_l[1])); + ASSERT_EQ ("send", std::get<0> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[2])); + ASSERT_EQ (node0->config.receive_minimum.to_string_dec (), std::get<2> (history_l[2])); + ASSERT_EQ (send->hash ().to_string (), std::get<3> (history_l[2])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[2])); + ASSERT_EQ (std::to_string (timestamp1), std::get<5> (history_l[2])); + // Genesis block + ASSERT_EQ ("receive", std::get<0> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<1> (history_l[3])); + ASSERT_EQ (nano::genesis_amount.convert_to (), std::get<2> (history_l[3])); + ASSERT_EQ (genesis.hash ().to_string (), std::get<3> (history_l[3])); + ASSERT_EQ (nano::test_genesis_key.pub.to_account (), std::get<4> (history_l[3])); +} diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index a714d7ac..86058b69 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -158,6 +158,8 @@ std::string nano::error_rpc_messages::message (int ev) const return "Invalid root hash"; case nano::error_rpc::invalid_sources: return "Invalid sources number"; + case nano::error_rpc::invalid_timestamp: + return "Invalid timestamp"; case nano::error_rpc::payment_account_balance: return "Account has non-zero balance"; case nano::error_rpc::payment_unable_create_account: diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 50638faa..632f4fc7 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -95,6 +95,7 @@ enum class error_rpc invalid_missing_type, invalid_root, invalid_sources, + invalid_timestamp, payment_account_balance, payment_unable_create_account, rpc_control_disabled, diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index bcf76639..0f0ad624 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -1981,7 +1981,10 @@ void nano::rpc_handler::ledger () boost::optional modified_since_text (request.get_optional ("modified_since")); if (modified_since_text.is_initialized ()) { - modified_since = strtoul (modified_since_text.get ().c_str (), NULL, 10); + if (decode_unsigned (modified_since_text.get (), modified_since)) + { + ec = nano::error_rpc::invalid_timestamp; + } } const bool sorting = request.get ("sorting", false); const bool representative = request.get ("representative", false); @@ -3381,6 +3384,64 @@ void nano::rpc_handler::wallet_frontiers () response_errors (); } +void nano::rpc_handler::wallet_history () +{ + uint64_t modified_since (1); + boost::optional modified_since_text (request.get_optional ("modified_since")); + if (modified_since_text.is_initialized ()) + { + if (decode_unsigned (modified_since_text.get (), modified_since)) + { + ec = nano::error_rpc::invalid_timestamp; + } + } + auto wallet (wallet_impl ()); + if (!ec) + { + std::multimap> entries; + auto transaction (node.wallets.tx_begin_read ()); + auto block_transaction (node.store.tx_begin_read ()); + for (auto i (wallet->store.begin (transaction)), n (wallet->store.end ()); i != n; ++i) + { + nano::account account (i->first); + nano::account_info info; + if (!node.store.account_get (block_transaction, account, info)) + { + auto timestamp (info.modified); + auto hash (info.head); + while (timestamp >= modified_since && timestamp != std::numeric_limits::max () && !hash.is_zero ()) + { + nano::block_sideband sideband; + auto block (node.store.block_get (block_transaction, hash, &sideband)); + timestamp = sideband.timestamp; + if (block != nullptr && timestamp >= modified_since && timestamp != std::numeric_limits::max ()) + { + boost::property_tree::ptree entry; + entry.put ("wallet_account", account.to_account ()); + entry.put ("hash", hash.to_string ()); + history_visitor visitor (*this, false, block_transaction, entry, hash); + block->visit (visitor); + entry.put ("local_timestamp", std::to_string (timestamp)); + entries.insert (std::make_pair (timestamp, entry)); + hash = block->previous (); + } + else + { + hash.clear (); + } + } + } + } + boost::property_tree::ptree history; + for (auto i (entries.begin ()), n (entries.end ()); i != n; ++i) + { + history.push_back (std::make_pair ("", i->second)); + } + response_l.add_child ("history", history); + } + response_errors (); +} + void nano::rpc_handler::wallet_key_valid () { auto wallet (wallet_impl ()); @@ -4346,6 +4407,10 @@ void nano::rpc_handler::process_request () { wallet_frontiers (); } + else if (action == "wallet_history") + { + wallet_history (); + } else if (action == "wallet_info") { wallet_info (); diff --git a/nano/node/rpc.hpp b/nano/node/rpc.hpp index f2e8abe4..90c58a85 100644 --- a/nano/node/rpc.hpp +++ b/nano/node/rpc.hpp @@ -207,6 +207,7 @@ public: void wallet_destroy (); void wallet_export (); void wallet_frontiers (); + void wallet_history (); void wallet_info (); void wallet_key_valid (); void wallet_ledger ();