diff --git a/rai/core_test/rpc.cpp b/rai/core_test/rpc.cpp index 6c6673bc..9d5bcd90 100644 --- a/rai/core_test/rpc.cpp +++ b/rai/core_test/rpc.cpp @@ -900,15 +900,15 @@ TEST (rpc, history_count) TEST (rpc, process_block) { - rai::system system (24000, 1); + rai::system system (24000, 1); rai::keypair key; auto latest (system.nodes [0]->latest (rai::test_genesis_key.pub)); auto & node1 (*system.nodes [0]); rai::send_block send (latest, key.pub, 100, rai::test_genesis_key.prv, rai::test_genesis_key.pub, node1.generate_work (latest)); - rai::rpc rpc (system.service, node1, rai::rpc_config (true)); + rai::rpc rpc (system.service, node1, rai::rpc_config (true)); rpc.start (); - boost::property_tree::ptree request; - request.put ("action", "process"); + boost::property_tree::ptree request; + request.put ("action", "process"); std::string json; send.serialize_json (json); request.put ("block", json); @@ -917,8 +917,10 @@ TEST (rpc, process_block) { system.poll (); } - ASSERT_EQ (200, response.status); + ASSERT_EQ (200, response.status); ASSERT_EQ (send.hash (), system.nodes [0]->latest (rai::test_genesis_key.pub)); + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); } TEST (rpc, process_block_no_work) @@ -1286,6 +1288,27 @@ TEST (rpc, pending) ASSERT_EQ (200, response1.status); blocks_node = response1.json.get_child ("blocks"); ASSERT_EQ (0, blocks_node.size ()); + request.put ("threshold", "0"); + request.put ("source", "true"); + test_response response2 (request, rpc, system.service); + while (response2.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response2.status); + blocks_node = response2.json.get_child ("blocks"); + ASSERT_EQ (1, blocks_node.size ()); + std::unordered_map amounts; + std::unordered_map sources; + for (auto i (blocks_node.begin ()), j (blocks_node.end ()); i != j; ++i) + { + rai::block_hash hash; + hash.decode_hex (i->first); + amounts[hash].decode_dec (i->second.get ("amount")); + sources[hash].decode_account (i->second.get ("source")); + } + ASSERT_EQ (amounts[block1->hash ()], 100); + ASSERT_EQ (sources[block1->hash ()], rai::test_genesis_key.pub); } TEST (rpc_config, serialization) @@ -1790,18 +1813,18 @@ TEST (rpc, account_remove) TEST (rpc, representatives) { - rai::system system0 (24000, 1); - rai::rpc rpc (system0.service, *system0.nodes [0], rai::rpc_config (true)); + rai::system system0 (24000, 1); + rai::rpc rpc (system0.service, *system0.nodes [0], rai::rpc_config (true)); rpc.start (); - boost::property_tree::ptree request; + boost::property_tree::ptree request; request.put ("action", "representatives"); test_response response (request, rpc, system0.service); while (response.status == 0) { system0.poll (); } - ASSERT_EQ (200, response.status); - auto & representatives_node (response.json.get_child ("representatives")); + ASSERT_EQ (200, response.status); + auto & representatives_node (response.json.get_child ("representatives")); std::vector representatives; for (auto i (representatives_node.begin ()), n (representatives_node.end ()); i != n; ++i) { @@ -1959,7 +1982,7 @@ TEST (rpc, bootstrap_any) ASSERT_TRUE (success.empty()); } -TEST (rpc, DISABLED_republish) +TEST (rpc, republish) { rai::system system (24000, 2); rai::keypair key; @@ -2164,11 +2187,11 @@ TEST (rpc, accounts_pending) system.poll (); } ASSERT_EQ (200, response1.status); + std::unordered_map blocks; for (auto & pending : response1.json.get_child("blocks")) { std::string account_text (pending.first); ASSERT_EQ (key1.pub.to_account (), account_text); - std::unordered_map blocks; for (auto i (pending.second.begin ()), j (pending.second.end ()); i != j; ++i) { rai::block_hash hash; @@ -2177,8 +2200,31 @@ TEST (rpc, accounts_pending) amount.decode_dec (i->second.get ("")); blocks [hash] = amount; } - ASSERT_EQ (blocks[block1->hash ()], 100); } + ASSERT_EQ (blocks[block1->hash ()], 100); + request.put ("source", "true"); + test_response response2 (request, rpc, system.service); + while (response2.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response2.status); + std::unordered_map amounts; + std::unordered_map sources; + for (auto & pending : response2.json.get_child("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.end ()); i != j; ++i) + { + rai::block_hash hash; + hash.decode_hex (i->first); + amounts[hash].decode_dec (i->second.get ("amount")); + sources[hash].decode_account (i->second.get ("source")); + } + } + ASSERT_EQ (amounts[block1->hash ()], 100); + ASSERT_EQ (sources[block1->hash ()], rai::test_genesis_key.pub); } TEST (rpc, blocks) @@ -2256,6 +2302,25 @@ TEST (rpc, wallet_balances) std::string pending_text (balances.second.get ("pending")); ASSERT_EQ ("0", pending_text); } + rai::keypair key; + system0.wallet (0)->insert_adhoc (key.prv); + auto send (system0.wallet (0)->send_action (rai::test_genesis_key.pub, key.pub, 1)); + request.put ("threshold", "2"); + test_response response1 (request, rpc, system0.service); + while (response1.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response1.status); + for (auto & balances : response1.json.get_child("balances")) + { + std::string account_text (balances.first); + ASSERT_EQ (rai::test_genesis_key.pub.to_account (), account_text); + std::string balance_text (balances.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211454", balance_text); + std::string pending_text (balances.second.get ("pending")); + ASSERT_EQ ("0", pending_text); + } } TEST (rpc, pending_exists) @@ -2294,6 +2359,7 @@ TEST (rpc, wallet_pending) rai::system system0 (24000, 1); rai::keypair key1; system0.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + system0.wallet (0)->insert_adhoc (key1.prv); auto block1 (system0.wallet (0)->send_action (rai::test_genesis_key.pub, key1.pub, 100)); rai::rpc rpc (system0.service, *system0.nodes [0], rai::rpc_config (true)); rpc.start (); @@ -2310,10 +2376,8 @@ TEST (rpc, wallet_pending) for (auto & pending : response.json.get_child("blocks")) { std::string account_text (pending.first); - ASSERT_EQ (rai::test_genesis_key.pub.to_account (), account_text); - auto & blocks_node (pending.second.get_child (rai::test_genesis_key.pub.to_account ())); - ASSERT_EQ (1, blocks_node.size ()); - rai::block_hash hash1 (blocks_node.begin ()->second.get ("")); + ASSERT_EQ (key1.pub.to_account (), account_text); + rai::block_hash hash1 (pending.second.begin ()->second.get ("")); ASSERT_EQ (block1->hash (), hash1); } request.put ("threshold", "100"); // Threshold test @@ -2323,11 +2387,11 @@ TEST (rpc, wallet_pending) system0.poll (); } ASSERT_EQ (200, response0.status); + std::unordered_map blocks; for (auto & pending : response0.json.get_child("blocks")) { std::string account_text (pending.first); - ASSERT_EQ (rai::test_genesis_key.pub.to_account (), account_text); - std::unordered_map blocks; + ASSERT_EQ (key1.pub.to_account (), account_text); for (auto i (pending.second.begin ()), j (pending.second.end ()); i != j; ++i) { rai::block_hash hash; @@ -2336,8 +2400,8 @@ TEST (rpc, wallet_pending) amount.decode_dec (i->second.get ("")); blocks [hash] = amount; } - ASSERT_EQ (blocks[block1->hash ()], 100); } + ASSERT_EQ (blocks[block1->hash ()], 100); request.put ("threshold", "101"); test_response response1 (request, rpc, system0.service); while (response1.status == 0) @@ -2347,6 +2411,30 @@ TEST (rpc, wallet_pending) ASSERT_EQ (200, response1.status); auto & pending1 (response1.json.get_child ("blocks")); ASSERT_EQ (0, pending1.size ()); + request.put ("threshold", "0"); + request.put ("source", "true"); + test_response response2 (request, rpc, system0.service); + while (response2.status == 0) + { + system0.poll (); + } + ASSERT_EQ (200, response2.status); + std::unordered_map amounts; + std::unordered_map sources; + for (auto & pending : response2.json.get_child("blocks")) + { + std::string account_text (pending.first); + ASSERT_EQ (key1.pub.to_account (), account_text); + for (auto i (pending.second.begin ()), j (pending.second.end ()); i != j; ++i) + { + rai::block_hash hash; + hash.decode_hex (i->first); + amounts[hash].decode_dec (i->second.get ("amount")); + sources[hash].decode_account (i->second.get ("source")); + } + } + ASSERT_EQ (amounts[block1->hash ()], 100); + ASSERT_EQ (sources[block1->hash ()], rai::test_genesis_key.pub); } TEST (rpc, receive_minimum) @@ -2487,7 +2575,7 @@ TEST (rpc, search_pending_all) } } -TEST (rpc, DISABLED_wallet_republish) +TEST (rpc, wallet_republish) { rai::system system (24000, 1); rai::genesis genesis; @@ -2713,3 +2801,267 @@ TEST (rpc, work_peers_all) peers_node = response3.json.get_child ("work_peers"); ASSERT_EQ (0, peers_node.size ()); } + +TEST (rpc, block_count_type) +{ + rai::system system (24000, 1); + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + auto send (system.wallet (0)->send_action (rai::test_genesis_key.pub, rai::test_genesis_key.pub, system.nodes [0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, send); + auto receive (system.wallet (0)->receive_action (static_cast (*send), rai::test_genesis_key.pub, system.nodes [0]->config.receive_minimum.number ())); + ASSERT_NE (nullptr, receive); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_count_type"); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string send_count (response.json.get ("send")); + ASSERT_EQ ("1", send_count); + std::string receive_count (response.json.get ("receive")); + ASSERT_EQ ("1", receive_count); + std::string open_count (response.json.get ("open")); + ASSERT_EQ ("1", open_count); + std::string change_count (response.json.get ("change")); + ASSERT_EQ ("0", change_count); +} + +TEST (rpc, ledger) +{ + rai::system system (24000, 1); + rai::keypair key; + rai::genesis genesis; + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes [0]); + auto latest (system.nodes [0]->latest (rai::test_genesis_key.pub)); + rai::send_block send (latest, key.pub, 100, rai::test_genesis_key.prv, rai::test_genesis_key.pub, node1.generate_work (latest)); + system.nodes [0]->process (send); + rai::open_block open (send.hash (), rai::test_genesis_key.pub, key.pub, key.prv, key.pub, node1.generate_work (key.pub)); + ASSERT_EQ (rai::process_result::progress, system.nodes [0]->process (open).code); + auto time (std::chrono::system_clock::to_time_t(std::chrono::system_clock::now())); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "ledger"); + request.put ("sorting", "1"); + request.put ("count", "1"); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + for (auto & accounts : response.json.get_child("accounts")) + { + std::string account_text (accounts.first); + ASSERT_EQ (key.pub.to_account (), account_text); + std::string frontier (accounts.second.get ("frontier")); + ASSERT_EQ (open.hash ().to_string (), frontier); + std::string open_block (accounts.second.get ("open_block")); + ASSERT_EQ (open.hash ().to_string (), open_block); + std::string representative_block (accounts.second.get ("representative_block")); + ASSERT_EQ (open.hash ().to_string (), representative_block); + std::string balance_text (accounts.second.get ("balance")); + ASSERT_EQ ("340282366920938463463374607431768211355", balance_text); + std::string modified_timestamp (accounts.second.get ("modified_timestamp")); + ASSERT_EQ (std::to_string (time), modified_timestamp); + std::string block_count (accounts.second.get ("block_count")); + ASSERT_EQ ("1", block_count); + } +} + +TEST (rpc, accounts_create) +{ + rai::system system (24000, 1); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "accounts_create"); + request.put ("wallet", system.nodes [0]->wallets.items.begin ()->first.to_string ()); + request.put ("count", "8"); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + auto & accounts (response.json.get_child ("accounts")); + for (auto i (accounts.begin ()), n (accounts.end ()); i != n; ++i) + { + std::string account_text (i->second.get ("")); + rai::uint256_union account; + ASSERT_FALSE (account.decode_account (account_text)); + ASSERT_TRUE (system.wallet (0)->exists (account)); + } + ASSERT_EQ (8, accounts.size ()); +} + +TEST (rpc, block_create) +{ + rai::system system (24000, 1); + rai::keypair key; + rai::genesis genesis; + system.wallet (0)->insert_adhoc (rai::test_genesis_key.prv); + system.wallet (0)->insert_adhoc (key.prv); + auto & node1 (*system.nodes [0]); + auto latest (system.nodes [0]->latest (rai::test_genesis_key.pub)); + auto send_work = node1.generate_work (latest); + rai::send_block send (latest, key.pub, 100, rai::test_genesis_key.prv, rai::test_genesis_key.pub, send_work); + auto open_work = node1.generate_work (key.pub); + rai::open_block open (send.hash (), rai::test_genesis_key.pub, key.pub, key.prv, key.pub, open_work); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + request.put ("action", "block_create"); + request.put ("type", "send"); + request.put ("wallet", system.nodes [0]->wallets.items.begin ()->first.to_string ()); + request.put ("account", rai::test_genesis_key.pub.to_account ()); + request.put ("previous", latest.to_string ()); + request.put ("amount", "340282366920938463463374607431768211355"); + request.put ("destination", key.pub.to_account ()); + request.put ("work", rai::to_string_hex (send_work)); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string send_hash (response.json.get ("hash")); + ASSERT_EQ (send.hash ().to_string (), send_hash); + auto send_text (response.json.get ("block")); + boost::property_tree::ptree block_l; + std::stringstream block_stream (send_text); + boost::property_tree::read_json (block_stream, block_l); + auto send_block (rai::deserialize_block_json (block_l)); + ASSERT_EQ (send.hash (), send_block->hash ()); + system.nodes [0]->process (send); + boost::property_tree::ptree request1; + request1.put ("action", "block_create"); + request1.put ("type", "open"); + std::string key_text; + key.prv.data.encode_hex (key_text); + request1.put ("key", key_text); + request1.put ("representative", rai::test_genesis_key.pub.to_account ()); + request1.put ("source", send.hash ().to_string ()); + request1.put ("work", rai::to_string_hex (open_work)); + test_response response1 (request1, rpc, system.service); + while (response1.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response1.status); + std::string open_hash (response1.json.get ("hash")); + ASSERT_EQ (open.hash ().to_string (), open_hash); + auto open_text (response1.json.get ("block")); + std::stringstream block_stream1 (open_text); + boost::property_tree::read_json (block_stream1, block_l); + auto open_block (rai::deserialize_block_json (block_l)); + ASSERT_EQ (open.hash (), open_block->hash ()); + ASSERT_EQ (rai::process_result::progress, system.nodes [0]->process (open).code); + request1.put ("representative", key.pub.to_account ()); + test_response response2 (request1, rpc, system.service); + while (response2.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response2.status); + std::string open2_hash (response2.json.get ("hash")); + ASSERT_NE (open.hash ().to_string (), open2_hash); // different blocks with wrong representative + auto change_work = node1.generate_work (open.hash ()); + rai::change_block change (open.hash (), key.pub, key.prv, key.pub, change_work); + request1.put ("type", "change"); + request1.put ("work", rai::to_string_hex (change_work)); + test_response response3 (request1, rpc, system.service); + while (response3.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response3.status); + ASSERT_FALSE (response3.json.get ("error").empty ()); // error with missing previous block + request1.put ("previous", open.hash ().to_string ()); + test_response response4 (request1, rpc, system.service); + while (response4.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response4.status); + std::string change_hash (response4.json.get ("hash")); + ASSERT_EQ (change.hash ().to_string (), change_hash); + auto change_text (response4.json.get ("block")); + std::stringstream block_stream4 (change_text); + boost::property_tree::read_json (block_stream4, block_l); + auto change_block (rai::deserialize_block_json (block_l)); + ASSERT_EQ (change.hash (), change_block->hash ()); + ASSERT_EQ (rai::process_result::progress, node1.process (change).code); + rai::send_block send2 (send.hash (), key.pub, 0, rai::test_genesis_key.prv, rai::test_genesis_key.pub, node1.generate_work (send.hash ())); + ASSERT_EQ (rai::process_result::progress, system.nodes [0]->process (send2).code); + boost::property_tree::ptree request2; + request2.put ("action", "block_create"); + request2.put ("type", "receive"); + request2.put ("wallet", system.nodes [0]->wallets.items.begin ()->first.to_string ()); + request2.put ("account", key.pub.to_account ()); + request2.put ("source", send2.hash ().to_string ()); + request2.put ("previous", change.hash ().to_string ()); + request2.put ("work", rai::to_string_hex (node1.generate_work (change.hash ()))); + test_response response5 (request2, rpc, system.service); + while (response5.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response5.status); + std::string receive_hash (response4.json.get ("hash")); + auto receive_text (response5.json.get ("block")); + std::stringstream block_stream5 (change_text); + boost::property_tree::read_json (block_stream5, block_l); + auto receive_block (rai::deserialize_block_json (block_l)); + ASSERT_EQ (receive_hash, receive_block->hash ().to_string ()); + system.nodes [0]->process_active (std::move (receive_block)); + latest = system.nodes [0]->latest (key.pub); + ASSERT_EQ (receive_hash, latest.to_string ()); +} + +TEST (rpc, wallet_lock) +{ + rai::system system (24000, 1); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + system.nodes [0]->wallets.items.begin ()->first.encode_hex (wallet); + ASSERT_TRUE (system.wallet (0)->valid_password ()); + request.put ("wallet", wallet); + request.put ("action", "wallet_lock"); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "1"); + ASSERT_FALSE (system.wallet (0)->valid_password ()); +} + +TEST (rpc, wallet_locked) +{ + rai::system system (24000, 1); + rai::rpc rpc (system.service, *system.nodes [0], rai::rpc_config (true)); + rpc.start (); + boost::property_tree::ptree request; + std::string wallet; + system.nodes [0]->wallets.items.begin ()->first.encode_hex (wallet); + request.put ("wallet", wallet); + request.put ("action", "wallet_locked"); + test_response response (request, rpc, system.service); + while (response.status == 0) + { + system.poll (); + } + ASSERT_EQ (200, response.status); + std::string account_text1 (response.json.get ("locked")); + ASSERT_EQ (account_text1, "0"); +} diff --git a/rai/node/rpc.cpp b/rai/node/rpc.cpp index e90b659a..4def7a33 100755 --- a/rai/node/rpc.cpp +++ b/rai/node/rpc.cpp @@ -218,7 +218,13 @@ void rai::rpc_handler::account_create () auto existing (node.wallets.items.find (wallet)); if (existing != node.wallets.items.end ()) { - rai::account new_key (existing->second->deterministic_insert ()); + bool generate_work (true); + boost::optional work (request.get_optional ("work")); + if (work.is_initialized ()) + { + generate_work = work.get (); + } + rai::account new_key (existing->second->deterministic_insert (generate_work)); if (!new_key.is_zero ()) { boost::property_tree::ptree response_l; @@ -270,6 +276,24 @@ void rai::rpc_handler::account_info () auto error (account.decode_account (account_text)); if (!error) { + bool representative (false); + boost::optional representative_optional (request.get_optional ("representative")); + if (representative_optional.is_initialized ()) + { + representative = representative_optional.get (); + } + bool weight (false); + boost::optional weight_optional (request.get_optional ("weight")); + if (weight_optional.is_initialized ()) + { + weight = weight_optional.get (); + } + bool pending (false); + boost::optional pending_optional (request.get_optional ("pending")); + if (pending_optional.is_initialized ()) + { + pending = pending_optional.get (); + } rai::transaction transaction (node.store.environment, nullptr, false); rai::account_info info; if (!node.store.account_get (transaction, account, info)) @@ -283,6 +307,22 @@ void rai::rpc_handler::account_info () response_l.put ("balance", balance); response_l.put ("modified_timestamp", std::to_string (info.modified)); response_l.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_l.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_l.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_l.put ("pending", account_pending.convert_to ()); + } response (response_l); } else @@ -520,6 +560,36 @@ void rai::rpc_handler::account_representative_set () auto error (representative.decode_account (representative_text)); if (!error) { + uint64_t work (0); + boost::optional work_text (request.get_optional ("work")); + if (work_text.is_initialized ()) + { + auto work_error (rai::from_string_hex (work_text.get (), work)); + if (work_error) + { + error_response (response, "Bad work"); + } + } + if (work) + { + rai::transaction transaction (node.store.environment, nullptr, true); + rai::account_info info; + if (!node.store.account_get (transaction, account, info)) + { + if (!node.work.work_validate (info.head, work)) + { + existing->second->store.work_put (transaction, account, work); + } + else + { + error_response (response, "Invalid work"); + } + } + else + { + error_response (response, "Account not found"); + } + } auto response_a (response); wallet->change_async (account, representative, [response_a] (std::shared_ptr block) { @@ -531,7 +601,7 @@ void rai::rpc_handler::account_representative_set () boost::property_tree::ptree response_l; response_l.put ("block", hash.to_string ()); response_a (response_l); - }); + }, work == 0); } } else @@ -591,6 +661,64 @@ void rai::rpc_handler::accounts_balances () response (response_l); } +void rai::rpc_handler::accounts_create () +{ + if (rpc.config.enable_control) + { + std::string wallet_text (request.get ("wallet")); + rai::uint256_union wallet; + auto error (wallet.decode_hex (wallet_text)); + if (!error) + { + uint64_t count; + std::string count_text (request.get ("count")); + auto count_error (decode_unsigned (count_text, count)); + if (!count_error && count != 0) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + bool generate_work (true); + boost::optional work (request.get_optional ("work")); + if (work.is_initialized ()) + { + generate_work = work.get (); + } + boost::property_tree::ptree response_l; + boost::property_tree::ptree accounts; + for (auto i(0); accounts.size ()< count; ++i) + { + rai::account new_key (existing->second->deterministic_insert (generate_work)); + if (!new_key.is_zero ()) + { + boost::property_tree::ptree entry; + entry.put ("", new_key.to_account ()); + accounts.push_back (std::make_pair ("", entry)); + } + } + response_l.add_child ("accounts", accounts); + response (response_l); + } + else + { + error_response (response, "Wallet not found"); + } + } + else + { + error_response (response, "Invalid count limit"); + } + } + else + { + error_response (response, "Bad wallet number"); + } + } + else + { + error_response (response, "RPC control is disabled"); + } +} void rai::rpc_handler::accounts_frontiers () { @@ -623,31 +751,29 @@ void rai::rpc_handler::accounts_pending () { uint64_t count (std::numeric_limits ::max ()); rai::uint128_union threshold (0); - try + bool source (false); + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) { - std::string count_text (request.get ("count")); - auto error (decode_unsigned (count_text, count)); + auto error (decode_unsigned (count_text.get (), count)); if (error) { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } - catch (std::runtime_error &) + boost::optional threshold_text (request.get_optional ("threshold")); + if (threshold_text.is_initialized ()) { - // If there is no "count" in request - } - try - { - std::string threshold_text (request.get ("threshold")); - auto error_threshold (threshold.decode_dec (threshold_text)); + auto error_threshold (threshold.decode_dec (threshold_text.get ())); if (error_threshold) { error_response (response, "Bad threshold number"); } } - catch (std::runtime_error &) + boost::optional source_optional (request.get_optional ("source")); + if (source_optional.is_initialized ()) { - // If there is no "threshold" in request + source = source_optional.get (); } boost::property_tree::ptree response_l; boost::property_tree::ptree pending; @@ -663,7 +789,7 @@ void rai::rpc_handler::accounts_pending () for (auto i (node.store.pending_begin (transaction, rai::pending_key (account, 0))), n (node.store.pending_begin (transaction, rai::pending_key (end, 0))); i != n && peers_l.size () < count; ++i) { rai::pending_key key (i->first); - if (threshold.is_zero ()) + if (threshold.is_zero () && !source) { boost::property_tree::ptree entry; entry.put ("", key.hash.to_string ()); @@ -674,7 +800,17 @@ void rai::rpc_handler::accounts_pending () rai::pending_info info (i->second); if (info.amount.number () >= threshold.number ()) { - peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + if (source) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + pending_tree.put ("source", info.source.to_account ()); + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } } } } @@ -764,6 +900,18 @@ void rai::rpc_handler::blocks () void rai::rpc_handler::blocks_info () { + bool pending (false); + boost::optional pending_optional (request.get_optional ("pending")); + if (pending_optional.is_initialized ()) + { + pending = pending_optional.get (); + } + bool source (false); + boost::optional source_optional (request.get_optional ("source")); + if (source_optional.is_initialized ()) + { + source = source_optional.get (); + } std::vector hashes; boost::property_tree::ptree response_l; boost::property_tree::ptree blocks; @@ -786,6 +934,30 @@ void rai::rpc_handler::blocks_info () std::string contents; block->serialize_json (contents); entry.put ("contents", contents); + if (pending) + { + auto block_l (dynamic_cast (block.get ())); + bool exists (false); + if (block_l != nullptr) + { + auto destination (block_l->hashables.destination); + exists = node.store.pending_exists (transaction, rai::pending_key (destination, hash)); + } + entry.put ("pending", exists ? "1" : "0"); + } + if (source) + { + rai::block_hash source_hash (block->source ()); + std::unique_ptr block_a (node.store.block_get (transaction, source_hash)); + if (block_a != nullptr) + { + auto source_account (node.ledger.account (transaction, source_hash)); + entry.put ("source_account", source_account.to_account ()); + } + else { + entry.put ("source_account", "0"); + } + } blocks.push_back (std::make_pair (hash_text, entry)); } else @@ -848,6 +1020,255 @@ void rai::rpc_handler::block_count_type () response (response_l); } + +void rai::rpc_handler::block_create () +{ + if (rpc.config.enable_control) + { + std::string type (request.get ("type")); + rai::uint256_union wallet (0); + boost::optional wallet_text (request.get_optional ("wallet")); + if (wallet_text.is_initialized ()) + { + auto error (wallet.decode_hex (wallet_text.get ())); + if (error) + { + error_response (response, "Bad wallet number"); + } + } + rai::uint256_union account (0); + boost::optional account_text (request.get_optional ("account")); + if (account_text.is_initialized ()) + { + auto error_account (account.decode_account (account_text.get ())); + if (error_account) + { + error_response (response, "Bad account number"); + } + } + rai::uint256_union representative (0); + boost::optional representative_text (request.get_optional ("representative")); + if (representative_text.is_initialized ()) + { + auto error_representative (representative.decode_account (representative_text.get ())); + if (error_representative) + { + error_response (response, "Bad representative account"); + } + } + rai::uint256_union destination (0); + boost::optional destination_text (request.get_optional ("destination")); + if (destination_text.is_initialized ()) + { + auto error_destination (destination.decode_account (destination_text.get ())); + if (error_destination) + { + error_response (response, "Bad destination account"); + } + } + rai::block_hash source (0); + boost::optional source_text (request.get_optional ("source")); + if (source_text.is_initialized ()) + { + auto error_source (source.decode_hex (source_text.get ())); + if (error_source) + { + error_response (response, "Invalid source hash"); + } + } + rai::uint128_union amount (0); + boost::optional amount_text (request.get_optional ("amount")); + if (amount_text.is_initialized ()) + { + auto error_amount (amount.decode_dec (amount_text.get ())); + if (error_amount) + { + error_response (response, "Bad amount number"); + } + } + uint64_t work (0); + boost::optional work_text (request.get_optional ("work")); + if (work_text.is_initialized ()) + { + auto work_error (rai::from_string_hex (work_text.get (), work)); + if (work_error) + { + error_response (response, "Bad work"); + } + } + rai::raw_key prv; + prv.data.clear (); + rai::uint256_union previous (0); + rai::uint128_union balance (0); + if (wallet != 0 && account != 0) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + rai::transaction transaction (node.store.environment, nullptr, false); + auto unlock_check (existing->second->store.valid_password (transaction)); + if (unlock_check) + { + auto account_check (existing->second->store.find (transaction, account)); + if (account_check != existing->second->store.end ()) + { + existing->second->store.fetch (transaction, account, prv); + previous = node.ledger.latest (transaction, account); + balance = node.ledger.account_balance (transaction, account); + } + else + { + error_response (response, "Account not found in wallet"); + } + } + else + { + error_response (response, "Wallet is locked"); + } + } + else + { + error_response (response, "Wallet not found"); + } + } + boost::optional key_text (request.get_optional ("key")); + if (key_text.is_initialized ()) + { + auto error_key (prv.data.decode_hex (key_text.get ())); + if (error_key) + { + error_response (response, "Bad private key"); + } + } + boost::optional previous_text (request.get_optional ("previous")); + if (previous_text.is_initialized ()) + { + auto error_previous (previous.decode_hex (previous_text.get ())); + if (error_previous) + { + error_response (response, "Invalid previous hash"); + } + } + boost::optional balance_text (request.get_optional ("balance")); + if (balance_text.is_initialized ()) + { + auto error_balance (balance.decode_dec (balance_text.get ())); + if (error_balance) + { + error_response (response, "Bad balance number"); + } + } + if (prv.data != 0) + { + rai::uint256_union pub; + ed25519_publickey (prv.data.bytes.data (), pub.bytes.data ()); + if (type == "open") + { + if (representative != 0 && source != 0) + { + if (work == 0) + { + work = node.generate_work (pub); + } + rai::open_block open (source, representative, pub, prv, pub, work); + boost::property_tree::ptree response_l; + response_l.put ("hash", open.hash ().to_string ()); + std::string contents; + open.serialize_json (contents); + response_l.put ("block", contents); + response (response_l); + } + else + { + error_response (response, "Representative account and source hash required"); + } + } + else if (type == "receive") + { + if (source != 0 && previous != 0) + { + if (work == 0) + { + work = node.generate_work (previous); + } + rai::receive_block receive (previous, source, prv, pub, work); + boost::property_tree::ptree response_l; + response_l.put ("hash", receive.hash ().to_string ()); + std::string contents; + receive.serialize_json (contents); + response_l.put ("block", contents); + response (response_l); + } + else + { + error_response (response, "Previous hash and source hash required"); + } + } + else if (type == "change") + { + if (representative != 0 && previous != 0) + { + if (work == 0) + { + work = node.generate_work (previous); + } + rai::change_block change (previous, representative, prv, pub, work); + boost::property_tree::ptree response_l; + response_l.put ("hash", change.hash ().to_string ()); + std::string contents; + change.serialize_json (contents); + response_l.put ("block", contents); + response (response_l); + } + else + { + error_response (response, "Representative account and previous hash required"); + } + } + else if (type == "send") + { + if (destination != 0 && previous != 0 && balance != 0 && amount != 0) + { + if (balance.number () >= amount.number ()) + { + if (work == 0) + { + work = node.generate_work (previous); + } + rai::send_block send (previous, destination, balance.number () - amount.number (), prv, pub, work); + boost::property_tree::ptree response_l; + response_l.put ("hash", send.hash ().to_string ()); + std::string contents; + send.serialize_json (contents); + response_l.put ("block", contents); + response (response_l); + } + else + { + error_response (response, "Insufficient balance"); + } + } + else + { + error_response (response, "Destination account, previous hash, current balance and amount required"); + } + } + else + { + error_response (response, "Invalid block type"); + } + } + else + { + error_response (response, "Private key or local wallet and account required"); + } + } + else + { + error_response (response, "RPC control is disabled"); + } +} + void rai::rpc_handler::successors () { std::string block_text (request.get ("block")); @@ -1297,6 +1718,143 @@ void rai::rpc_handler::key_expand () } } +void rai::rpc_handler::ledger () +{ + if (rpc.config.enable_control) + { + rai::account start (0); + uint64_t count (std::numeric_limits ::max ()); + bool sorting (false); + boost::optional account_text (request.get_optional ("account")); + if (account_text.is_initialized ()) + { + auto error (start.decode_account (account_text.get ())); + if (error) + { + error_response (response, "Invalid starting account"); + } + } + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) + { + auto error_count (decode_unsigned (count_text.get (), count)); + if (error_count) + { + error_response (response, "Invalid count limit"); + } + } + boost::optional sorting_optional (request.get_optional ("sorting")); + if (sorting_optional.is_initialized ()) + { + sorting = sorting_optional.get (); + } + bool representative (false); + boost::optional representative_optional (request.get_optional ("representative")); + if (representative_optional.is_initialized ()) + { + representative = representative_optional.get (); + } + bool weight (false); + boost::optional weight_optional (request.get_optional ("weight")); + if (weight_optional.is_initialized ()) + { + weight = weight_optional.get (); + } + bool pending (false); + boost::optional pending_optional (request.get_optional ("pending")); + if (pending_optional.is_initialized ()) + { + pending = pending_optional.get (); + } + boost::property_tree::ptree response_a; + boost::property_tree::ptree response_l; + boost::property_tree::ptree accounts; + rai::transaction transaction (node.store.environment, nullptr, false); + if (!sorting) // Simple + { + for (auto i (node.store.latest_begin (transaction, start)), n (node.store.latest_end ()); i != n && accounts.size () < count; ++i) + { + rai::account_info info (i->second); + rai::account account (i->first); + boost::property_tree::ptree response_l; + response_l.put ("frontier", info.head.to_string ()); + response_l.put ("open_block", info.open_block.to_string ()); + response_l.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + rai::uint128_union (info.balance).encode_dec (balance); + response_l.put ("balance", balance); + response_l.put ("modified_timestamp", std::to_string (info.modified)); + response_l.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_l.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_l.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_l.put ("pending", account_pending.convert_to ()); + } + accounts.push_back (std::make_pair (account.to_account (), response_l)); + } + } + else // Sorting + { + std::vector > ledger_l; + for (auto i (node.store.latest_begin (transaction, start)), n (node.store.latest_end ()); i != n; ++i) + { + rai::uint128_union balance (rai::account_info (i->second).balance); + ledger_l.push_back (std::make_pair (balance, rai::account (i->first))); + } + std::sort (ledger_l.begin (), ledger_l.end ()); + std::reverse (ledger_l.begin (), ledger_l.end ()); + rai::account_info info; + for (auto i (ledger_l.begin ()), n (ledger_l.end ()); i != n && accounts.size () < count; ++i) + { + node.store.account_get (transaction, i->second, info); + rai::account account (i->second); + response_l.put ("frontier", info.head.to_string ()); + response_l.put ("open_block", info.open_block.to_string ()); + response_l.put ("representative_block", info.rep_block.to_string ()); + std::string balance; + (i->first).encode_dec (balance); + response_l.put ("balance", balance); + response_l.put ("modified_timestamp", std::to_string (info.modified)); + response_l.put ("block_count", std::to_string (info.block_count)); + if (representative) + { + auto block (node.store.block_get (transaction, info.rep_block)); + assert (block != nullptr); + response_l.put ("representative", block->representative ().to_account ()); + } + if (weight) + { + auto account_weight (node.ledger.weight (transaction, account)); + response_l.put ("weight", account_weight.convert_to ()); + } + if (pending) + { + auto account_pending (node.ledger.account_pending (transaction, account)); + response_l.put ("pending", account_pending.convert_to ()); + } + accounts.push_back (std::make_pair (account.to_account (), response_l)); + } + } + response_a.add_child ("accounts", accounts); + response (response_a); + } + else + { + error_response (response, "RPC control is disabled"); + } +} + void rai::rpc_handler::mrai_from_raw () { std::string amount_text (request.get ("amount")); @@ -1405,7 +1963,7 @@ void rai::rpc_handler::password_change () } else { - error_response (response, "Bad account number"); + error_response (response, "Bad wallet number"); } } else @@ -1437,11 +1995,11 @@ void rai::rpc_handler::password_enter () } else { - error_response (response, "Bad account number"); + error_response (response, "Bad wallet number"); } } -void rai::rpc_handler::password_valid () +void rai::rpc_handler::password_valid (bool wallet_locked = false) { std::string wallet_text (request.get ("wallet")); rai::uint256_union wallet; @@ -1453,7 +2011,15 @@ void rai::rpc_handler::password_valid () { rai::transaction transaction (node.store.environment, nullptr, false); boost::property_tree::ptree response_l; - response_l.put ("valid", existing->second->store.valid_password (transaction) ? "1" : "0"); + auto valid (existing->second->store.valid_password (transaction)); + if (!wallet_locked) + { + response_l.put ("valid", valid ? "1" : "0"); + } + else + { + response_l.put ("locked", valid ? "0" : "1"); + } response (response_l); } else @@ -1463,7 +2029,7 @@ void rai::rpc_handler::password_valid () } else { - error_response (response, "Bad account number"); + error_response (response, "Bad wallet number"); } } @@ -1490,31 +2056,29 @@ void rai::rpc_handler::pending () { uint64_t count (std::numeric_limits ::max ()); rai::uint128_union threshold (0); - try + bool source (false); + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) { - std::string count_text (request.get ("count")); - auto error (decode_unsigned (count_text, count)); + auto error (decode_unsigned (count_text.get (), count)); if (error) { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } - catch (std::runtime_error &) + boost::optional threshold_text (request.get_optional ("threshold")); + if (threshold_text.is_initialized ()) { - // If there is no "count" in request - } - try - { - std::string threshold_text (request.get ("threshold")); - auto error_threshold (threshold.decode_dec (threshold_text)); + auto error_threshold (threshold.decode_dec (threshold_text.get ())); if (error_threshold) { error_response (response, "Bad threshold number"); } } - catch (std::runtime_error &) + boost::optional source_optional (request.get_optional ("source")); + if (source_optional.is_initialized ()) { - // If there is no "threshold" in request + source = source_optional.get (); } boost::property_tree::ptree response_l; boost::property_tree::ptree peers_l; @@ -1524,7 +2088,7 @@ void rai::rpc_handler::pending () for (auto i (node.store.pending_begin (transaction, rai::pending_key (account, 0))), n (node.store.pending_begin (transaction, rai::pending_key (end, 0))); i != n && peers_l.size ()< count; ++i) { rai::pending_key key (i->first); - if (threshold.is_zero ()) + if (threshold.is_zero () && !source) { boost::property_tree::ptree entry; entry.put ("", key.hash.to_string ()); @@ -1535,7 +2099,17 @@ void rai::rpc_handler::pending () rai::pending_info info (i->second); if (info.amount.number () >= threshold.number ()) { - peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + if (source) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + pending_tree.put ("source", info.source.to_account ()); + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } } } } @@ -1788,9 +2362,75 @@ void rai::rpc_handler::process () { if (!node.work.work_validate (*block)) { - node.process_active (std::move (block)); - boost::property_tree::ptree response_l; - response (response_l); + auto hash (block->hash ()); + node.block_arrival.add (hash); + rai::process_return result; + std::shared_ptr block_a (std::move (block)); + { + rai::transaction transaction (node.store.environment, nullptr, true); + result = node.block_processor.process_receive_one (transaction, block_a); + } + switch (result.code) + { + case rai::process_result::progress: + { + node.observers.blocks (block_a, result.account, result.amount); + boost::property_tree::ptree response_l; + response_l.put ("hash", hash.to_string ()); + response (response_l); + break; + } + case rai::process_result::gap_previous: + { + error_response (response, "Gap previous block"); + break; + } + case rai::process_result::gap_source: + { + error_response (response, "Gap source block"); + break; + } + case rai::process_result::old: + { + error_response (response, "Old block"); + break; + } + case rai::process_result::bad_signature: + { + error_response (response, "Bad signature"); + break; + } + case rai::process_result::overspend: + { + error_response (response, "Overspend"); + break; + } + case rai::process_result::unreceivable: + { + error_response (response, "Unreceivable"); + break; + } + case rai::process_result::not_receive_from_send: + { + error_response (response, "Not receive from send"); + break; + } + case rai::process_result::fork: + { + error_response (response, "Fork"); + break; + } + case rai::process_result::account_mismatch: + { + error_response (response, "Account mismatch"); + break; + } + default: + { + error_response (response, "Error processing block"); + break; + } + } } else { @@ -1875,6 +2515,38 @@ void rai::rpc_handler::receive () { if (node.store.pending_exists (transaction, rai::pending_key (account, hash))) { + uint64_t work (0); + boost::optional work_text (request.get_optional ("work")); + if (work_text.is_initialized ()) + { + auto work_error (rai::from_string_hex (work_text.get (), work)); + if (work_error) + { + error_response (response, "Bad work"); + } + } + if (work) + { + rai::account_info info; + rai::uint256_union head; + if (!node.store.account_get (transaction, account, info)) + { + head = info.head; + } + else + { + head = account; + } + if (!node.work.work_validate (head, work)) + { + rai::transaction transaction_a (node.store.environment, nullptr, true); + existing->second->store.work_put (transaction_a, account, work); + } + else + { + error_response (response, "Invalid work"); + } + } auto response_a (response); existing->second->receive_async (std::move (block), account, rai::genesis_amount, [response_a] (std::shared_ptr block_a) { @@ -1886,7 +2558,7 @@ void rai::rpc_handler::receive () boost::property_tree::ptree response_l; response_l.put ("block", hash_a.to_string ()); response_a (response_l); - }); + }, work == 0); } else { @@ -1969,14 +2641,49 @@ void rai::rpc_handler::receive_minimum_set () void rai::rpc_handler::representatives () { + uint64_t count (std::numeric_limits ::max ()); + bool sorting (false); + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) + { + auto error (decode_unsigned (count_text.get (), count)); + if (error) + { + error_response (response, "Invalid count limit"); + } + } + boost::optional sorting_optional (request.get_optional ("sorting")); + if (sorting_optional.is_initialized ()) + { + sorting = sorting_optional.get (); + } boost::property_tree::ptree response_l; boost::property_tree::ptree representatives; rai::transaction transaction (node.store.environment, nullptr, false); - for (auto i (node.store.representation_begin (transaction)), n (node.store.representation_end ()); i != n; ++i) + if (!sorting) // Simple { - rai::account account(i->first); - auto amount (node.store.representation_get (transaction, account)); - representatives.put (account.to_account (), amount.convert_to ()); + for (auto i (node.store.representation_begin (transaction)), n (node.store.representation_end ()); i != n && representatives.size () < count; ++i) + { + rai::account account(i->first); + auto amount (node.store.representation_get (transaction, account)); + representatives.put (account.to_account (), amount.convert_to ()); + } + } + else // Sorting + { + std::vector > representation; + for (auto i (node.store.representation_begin (transaction)), n (node.store.representation_end ()); i != n; ++i) + { + rai::account account(i->first); + auto amount (node.store.representation_get (transaction, account)); + representation.push_back (std::make_pair (amount, account.to_account ())); + } + std::sort (representation.begin (), representation.end ()); + std::reverse (representation.begin (), representation.end ()); + for (auto i (representation.begin ()), n (representation.end ()); i != n && representatives.size () < count; ++i) + { + representatives.put (i->second, (i->first).number ().convert_to ()); + } } response_l.add_child ("representatives", representatives); response (response_l); @@ -2178,19 +2885,60 @@ void rai::rpc_handler::send () auto error (amount.decode_dec (amount_text)); if (!error) { - auto rpc_l (shared_from_this ()); - auto response_a (response); - existing->second->send_async (source, destination, amount.number (), [response_a] (std::shared_ptr block_a) + uint64_t work (0); + boost::optional work_text (request.get_optional ("work")); + if (work_text.is_initialized ()) { - rai::uint256_union hash (0); - if (block_a != nullptr) + auto work_error (rai::from_string_hex (work_text.get (), work)); + if (work_error) { - hash = block_a->hash (); + error_response (response, "Bad work"); } - boost::property_tree::ptree response_l; - response_l.put ("block", hash.to_string ()); - response_a (response_l); - }); + } + rai::uint128_t balance (0); + { + rai::transaction transaction (node.store.environment, nullptr, work != 0); // false if no "work" in request, true if work > 0 + rai::account_info info; + if (!node.store.account_get (transaction, source, info)) + { + balance = (info.balance).number(); + } + else + { + error_response (response, "Account not found"); + } + if (work) + { + if (!node.work.work_validate (info.head, work)) + { + existing->second->store.work_put (transaction, source, work); + } + else + { + error_response (response, "Invalid work"); + } + } + } + if (balance >= amount.number ()) + { + auto rpc_l (shared_from_this ()); + auto response_a (response); + existing->second->send_async (source, destination, amount.number (), [response_a] (std::shared_ptr block_a) + { + rai::uint256_union hash (0); + if (block_a != nullptr) + { + hash = block_a->hash (); + } + boost::property_tree::ptree response_l; + response_l.put ("block", hash.to_string ()); + response_a (response_l); + }, work == 0); + } + else + { + error_response (response, "Insufficient balance"); + } } else { @@ -2227,6 +2975,9 @@ void rai::rpc_handler::stop () { if (rpc.config.enable_control) { + boost::property_tree::ptree response_l; + response_l.put ("success", ""); + response (response_l); rpc.stop (); node.stop (); } @@ -2239,19 +2990,15 @@ void rai::rpc_handler::stop () void rai::rpc_handler::unchecked () { uint64_t count (std::numeric_limits ::max ()); - try + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) { - std::string count_text (request.get ("count")); - auto error (decode_unsigned (count_text, count)); + auto error (decode_unsigned (count_text.get (), count)); if (error) { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } - catch (std::runtime_error &) - { - // If there is no "count" in request - } boost::property_tree::ptree response_l; boost::property_tree::ptree unchecked; rai::transaction transaction (node.store.environment, nullptr, false); @@ -2322,33 +3069,25 @@ void rai::rpc_handler::unchecked_get () void rai::rpc_handler::unchecked_keys () { uint64_t count (std::numeric_limits ::max ()); - try + rai::uint256_union key (0); + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) { - std::string count_text (request.get ("count")); - auto error (decode_unsigned (count_text, count)); + auto error (decode_unsigned (count_text.get (), count)); if (error) { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } - catch (std::runtime_error &) + boost::optional hash_text (request.get_optional ("key")); + if (hash_text.is_initialized ()) { - // If there is no "count" in request - } - rai::uint256_union key (0); - try - { - std::string hash_text (request.get ("key")); - auto error_hash (key.decode_hex (hash_text)); + auto error_hash (key.decode_hex (hash_text.get ())); if (error_hash) { error_response (response, "Bad key hash number"); } } - catch (std::runtime_error &) - { - // If there is no "key" in request - } boost::property_tree::ptree response_l; boost::property_tree::ptree unchecked; rai::transaction transaction (node.store.environment, nullptr, false); @@ -2404,7 +3143,13 @@ void rai::rpc_handler::wallet_add () auto existing (node.wallets.items.find (wallet)); if (existing != node.wallets.items.end ()) { - auto pub (existing->second->insert_adhoc (key)); + bool generate_work (true); + boost::optional work (request.get_optional ("work")); + if (work.is_initialized ()) + { + generate_work = work.get (); + } + auto pub (existing->second->insert_adhoc (key, generate_work)); if (!pub.is_zero ()) { boost::property_tree::ptree response_l; @@ -2479,6 +3224,16 @@ void rai::rpc_handler::wallet_balances () auto error (wallet.decode_hex (wallet_text)); if (!error) { + rai::uint128_union threshold (0); + boost::optional threshold_text (request.get_optional ("threshold")); + if (threshold_text.is_initialized ()) + { + auto error_threshold (threshold.decode_dec (threshold_text.get ())); + if (error_threshold) + { + error_response (response, "Bad threshold number"); + } + } auto existing (node.wallets.items.find (wallet)); if (existing != node.wallets.items.end ()) { @@ -2488,12 +3243,26 @@ void rai::rpc_handler::wallet_balances () for (auto i (existing->second->store.begin (transaction)), n (existing->second->store.end ()); i != n; ++i) { rai::account account(i->first); - boost::property_tree::ptree entry; rai::uint128_t balance = node.ledger.account_balance (transaction, account); - rai::uint128_t pending = node.ledger.account_pending (transaction, account); - entry.put ("balance", balance.convert_to ()); - entry.put ("pending", pending.convert_to ()); - balances.push_back (std::make_pair (account.to_account (), entry)); + if (threshold.is_zero ()) + { + boost::property_tree::ptree entry; + rai::uint128_t pending = node.ledger.account_pending (transaction, account); + entry.put ("balance", balance.convert_to ()); + entry.put ("pending", pending.convert_to ()); + balances.push_back (std::make_pair (account.to_account (), entry)); + } + else + { + if (balance >= threshold.number ()) + { + boost::property_tree::ptree entry; + rai::uint128_t pending = node.ledger.account_pending (transaction, account); + entry.put ("balance", balance.convert_to ()); + entry.put ("pending", pending.convert_to ()); + balances.push_back (std::make_pair (account.to_account (), entry)); + } + } } response_l.add_child ("balances", balances); response (response_l); @@ -2736,6 +3505,41 @@ void rai::rpc_handler::wallet_key_valid () } } +void rai::rpc_handler::wallet_lock () +{ + if (rpc.config.enable_control) + { + std::string wallet_text (request.get ("wallet")); + rai::uint256_union wallet; + auto error (wallet.decode_hex (wallet_text)); + if (!error) + { + auto existing (node.wallets.items.find (wallet)); + if (existing != node.wallets.items.end ()) + { + boost::property_tree::ptree response_l; + rai::raw_key empty; + empty.data.clear (); + existing->second->store.password.value_set (empty); + response_l.put ("locked", "1"); + response (response_l); + } + else + { + error_response (response, "Wallet not found"); + } + } + else + { + error_response (response, "Bad wallet number"); + } + } + else + { + error_response (response, "RPC control is disabled"); + } +} + void rai::rpc_handler::wallet_pending () { std::string wallet_text (request.get ("wallet")); @@ -2748,31 +3552,29 @@ void rai::rpc_handler::wallet_pending () { uint64_t count (std::numeric_limits ::max ()); rai::uint128_union threshold (0); - try + bool source (false); + boost::optional count_text (request.get_optional ("count")); + if (count_text.is_initialized ()) { - std::string count_text (request.get ("count")); - auto error_count (decode_unsigned (count_text, count)); - if (error_count) + auto error (decode_unsigned (count_text.get (), count)); + if (error) { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } - catch (std::runtime_error &) + boost::optional threshold_text (request.get_optional ("threshold")); + if (threshold_text.is_initialized ()) { - // If there is no "count" in request - } - try - { - std::string threshold_text (request.get ("threshold")); - auto error_threshold (threshold.decode_dec (threshold_text)); + auto error_threshold (threshold.decode_dec (threshold_text.get ())); if (error_threshold) { error_response (response, "Bad threshold number"); } } - catch (std::runtime_error &) + boost::optional source_optional (request.get_optional ("source")); + if (source_optional.is_initialized ()) { - // If there is no "threshold" in request + source = source_optional.get (); } boost::property_tree::ptree response_l; boost::property_tree::ptree pending; @@ -2785,7 +3587,7 @@ void rai::rpc_handler::wallet_pending () for (auto ii (node.store.pending_begin (transaction, rai::pending_key (account, 0))), nn (node.store.pending_begin (transaction, rai::pending_key (end, 0))); ii != nn && peers_l.size ()< count; ++ii) { rai::pending_key key (ii->first); - if (threshold.is_zero ()) + if (threshold.is_zero () && !source) { boost::property_tree::ptree entry; entry.put ("", key.hash.to_string ()); @@ -2796,7 +3598,17 @@ void rai::rpc_handler::wallet_pending () rai::pending_info info (ii->second); if (info.amount.number () >= threshold.number ()) { - peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + if (source) + { + boost::property_tree::ptree pending_tree; + pending_tree.put ("amount", info.amount.number ().convert_to ()); + pending_tree.put ("source", info.source.to_account ()); + peers_l.add_child (key.hash.to_string (), pending_tree); + } + else + { + peers_l.put (key.hash.to_string (), info.amount.number ().convert_to ()); + } } } } @@ -2936,7 +3748,7 @@ void rai::rpc_handler::wallet_republish () } else { - error_response (response, "Invalid count"); + error_response (response, "Invalid count limit"); } } else @@ -3350,6 +4162,12 @@ void rai::rpc_handler::process_request () request.erase ("password"); reprocess_body (body, request); } + else if (action == "wallet_unlock") + { + password_enter (); + request.erase ("password"); + reprocess_body (body, request); + } if (node.config.logging.log_rpc ()) { BOOST_LOG (node.log) << body; @@ -3410,6 +4228,10 @@ void rai::rpc_handler::process_request () { accounts_balances (); } + else if (action == "accounts_create") + { + accounts_create (); + } else if (action == "accounts_frontiers") { accounts_frontiers (); @@ -3446,6 +4268,10 @@ void rai::rpc_handler::process_request () { block_count_type (); } + else if (action == "block_create") + { + block_create (); + } else if (action == "successors") { successors (); @@ -3506,6 +4332,10 @@ void rai::rpc_handler::process_request () { krai_to_raw (); } + else if (action == "ledger") + { + ledger (); + } else if (action == "mrai_from_raw") { mrai_from_raw (); @@ -3666,6 +4496,14 @@ void rai::rpc_handler::process_request () { wallet_key_valid (); } + else if (action == "wallet_lock") + { + wallet_lock (); + } + else if (action == "wallet_locked") + { + password_valid (true); + } else if (action == "wallet_pending") { wallet_pending (); @@ -3682,6 +4520,10 @@ void rai::rpc_handler::process_request () { wallet_republish (); } + else if (action == "wallet_unlock") + { + // Processed before logging + } else if (action == "wallet_work_get") { wallet_work_get (); diff --git a/rai/node/rpc.hpp b/rai/node/rpc.hpp index 8c17e77c..1b254ccc 100644 --- a/rai/node/rpc.hpp +++ b/rai/node/rpc.hpp @@ -104,6 +104,7 @@ public: void account_representative_set (); void account_weight (); void accounts_balances (); + void accounts_create (); void accounts_frontiers (); void accounts_pending (); void available_supply (); @@ -113,6 +114,7 @@ public: void block_account (); void block_count (); void block_count_type (); + void block_create (); void bootstrap (); void bootstrap_any (); void chain (); @@ -127,11 +129,12 @@ public: void key_expand (); void krai_to_raw (); void krai_from_raw (); + void ledger (); void mrai_to_raw (); void mrai_from_raw (); void password_change (); void password_enter (); - void password_valid (); + void password_valid (bool wallet_locked); void payment_begin (); void payment_init (); void payment_end (); @@ -168,6 +171,7 @@ public: void wallet_export (); void wallet_frontiers (); void wallet_key_valid (); + void wallet_lock (); void wallet_pending (); void wallet_representative (); void wallet_representative_set (); diff --git a/rai/node/wallet.cpp b/rai/node/wallet.cpp index 1ec6bf1b..b2f26a4b 100644 --- a/rai/node/wallet.cpp +++ b/rai/node/wallet.cpp @@ -908,38 +908,68 @@ bool rai::wallet::enter_password (std::string const & password_a) } rai::public_key rai::wallet::deterministic_insert (MDB_txn * transaction_a) +{ + auto result (deterministic_insert(transaction_a, true)); + return result; +} + +rai::public_key rai::wallet::deterministic_insert (MDB_txn * transaction_a, bool const & generate_work_a) { rai::public_key key (0); if (store.valid_password (transaction_a)) { key = store.deterministic_insert (transaction_a); - work_ensure (transaction_a, key); + if (generate_work_a) + { + work_ensure (transaction_a, key); + } } return key; } rai::public_key rai::wallet::deterministic_insert () +{ + auto result (deterministic_insert (true)); + return result; +} + +rai::public_key rai::wallet::deterministic_insert (bool const & generate_work_a) { rai::transaction transaction (store.environment, nullptr, true); - auto result (deterministic_insert (transaction)); + auto result (deterministic_insert (transaction, generate_work_a)); return result; } rai::public_key rai::wallet::insert_adhoc (MDB_txn * transaction_a, rai::raw_key const & key_a) +{ + auto result (insert_adhoc (transaction_a, key_a, true)); + return result; +} + +rai::public_key rai::wallet::insert_adhoc (MDB_txn * transaction_a, rai::raw_key const & key_a, bool const & generate_work_a) { rai::public_key key (0); if (store.valid_password (transaction_a)) { key = store.insert_adhoc (transaction_a, key_a); - work_ensure (transaction_a, key); + if (generate_work_a) + { + work_ensure (transaction_a, key); + } } return key; } rai::public_key rai::wallet::insert_adhoc (rai::raw_key const & account_a) +{ + auto result (insert_adhoc (account_a, true)); + return result; +} + +rai::public_key rai::wallet::insert_adhoc (rai::raw_key const & account_a, bool const & generate_work_a) { rai::transaction transaction (store.environment, nullptr, true); - auto result (insert_adhoc (transaction, account_a)); + auto result (insert_adhoc (transaction, account_a, generate_work_a)); return result; } @@ -994,6 +1024,11 @@ bool check_ownership (rai::wallets & wallets_a, rai::account const & account_a) } std::shared_ptr rai::wallet::receive_action (rai::send_block const & send_a, rai::account const & representative_a, rai::uint128_union const & amount_a) +{ + return receive_action (send_a, representative_a, amount_a, true); +} + +std::shared_ptr rai::wallet::receive_action (rai::send_block const & send_a, rai::account const & representative_a, rai::uint128_union const & amount_a, bool const & generate_work_a) { auto hash (send_a.hash ()); std::shared_ptr block; @@ -1037,17 +1072,30 @@ std::shared_ptr rai::wallet::receive_action (rai::send_block const assert (block != nullptr); node.process_active (block); auto hash (block->hash ()); - auto this_l (shared_from_this ()); auto source (send_a.hashables.destination); - node.wallets.queue_wallet_action (source, rai::wallets::generate_priority, [this_l, source, hash] + if (generate_work_a) { - this_l->work_generate (source, hash); - }); + auto this_l (shared_from_this ()); + node.wallets.queue_wallet_action (source, rai::wallets::generate_priority, [this_l, source, hash] + { + this_l->work_generate (source, hash); + }); + } + else + { + rai::transaction transaction (store.environment, nullptr, true); + work_update (transaction, source, hash, 0); + } } return block; } std::shared_ptr rai::wallet::change_action (rai::account const & source_a, rai::account const & representative_a) +{ + return change_action (source_a, representative_a, true); +} + +std::shared_ptr rai::wallet::change_action (rai::account const & source_a, rai::account const & representative_a, bool const & generate_work_a) { std::shared_ptr block; { @@ -1075,16 +1123,29 @@ std::shared_ptr rai::wallet::change_action (rai::account const & so assert (block != nullptr); node.process_active (block); auto hash (block->hash ()); - auto this_l (shared_from_this ()); - node.wallets.queue_wallet_action (source_a, rai::wallets::generate_priority, [this_l, source_a, hash] + if (generate_work_a) { - this_l->work_generate (source_a, hash); - }); + auto this_l (shared_from_this ()); + node.wallets.queue_wallet_action (source_a, rai::wallets::generate_priority, [this_l, source_a, hash] + { + this_l->work_generate (source_a, hash); + }); + } + else + { + rai::transaction transaction (store.environment, nullptr, true); + work_update (transaction, source_a, hash, 0); + } } return block; } std::shared_ptr rai::wallet::send_action (rai::account const & source_a, rai::account const & account_a, rai::uint128_t const & amount_a) +{ + return send_action (source_a, account_a, amount_a, true); +} + +std::shared_ptr rai::wallet::send_action (rai::account const & source_a, rai::account const & account_a, rai::uint128_t const & amount_a, bool const & generate_work_a) { std::shared_ptr block; { @@ -1116,11 +1177,19 @@ std::shared_ptr rai::wallet::send_action (rai::account const & sour assert (block != nullptr); node.process_active (block); auto hash (block->hash ()); - auto this_l (shared_from_this ()); - node.wallets.queue_wallet_action (source_a, rai::wallets::generate_priority, [this_l, source_a, hash] + if (generate_work_a) { - this_l->work_generate (source_a, hash); - }); + auto this_l (shared_from_this ()); + node.wallets.queue_wallet_action (source_a, rai::wallets::generate_priority, [this_l, source_a, hash] + { + this_l->work_generate (source_a, hash); + }); + } + else + { + rai::transaction transaction (store.environment, nullptr, true); + work_update (transaction, source_a, hash, 0); + } } return block; } @@ -1137,10 +1206,15 @@ bool rai::wallet::change_sync (rai::account const & source_a, rai::account const void rai::wallet::change_async (rai::account const & source_a, rai::account const & representative_a, std::function )> const & action_a) { - node.wallets.queue_wallet_action (source_a, rai::wallets::high_priority, [this, source_a, representative_a, action_a] () + change_async (source_a, representative_a, action_a, true); +} + +void rai::wallet::change_async (rai::account const & source_a, rai::account const & representative_a, std::function )> const & action_a, bool const & generate_work_a) +{ + node.wallets.queue_wallet_action (source_a, rai::wallets::high_priority, [this, source_a, representative_a, action_a, generate_work_a] () { assert (!check_ownership (node.wallets, source_a)); - auto block (change_action (source_a, representative_a)); + auto block (change_action (source_a, representative_a, generate_work_a)); action_a (block); }); } @@ -1156,12 +1230,17 @@ bool rai::wallet::receive_sync (std::shared_ptr block_a, rai::accou } void rai::wallet::receive_async (std::shared_ptr block_a, rai::account const & representative_a, rai::uint128_t const & amount_a, std::function )> const & action_a) +{ + receive_async (block_a, representative_a, amount_a, action_a, true); +} + +void rai::wallet::receive_async (std::shared_ptr block_a, rai::account const & representative_a, rai::uint128_t const & amount_a, std::function )> const & action_a, bool const & generate_work_a) { assert (dynamic_cast (block_a.get ()) != nullptr); - node.wallets.queue_wallet_action (static_cast (block_a.get ())->hashables.destination, amount_a, [this, block_a, representative_a, amount_a, action_a] () + node.wallets.queue_wallet_action (static_cast (block_a.get ())->hashables.destination, amount_a, [this, block_a, representative_a, amount_a, action_a, generate_work_a] () { assert (!check_ownership (node.wallets, static_cast (block_a.get ())->hashables.destination)); - auto block (receive_action (*static_cast (block_a.get ()), representative_a, amount_a)); + auto block (receive_action (*static_cast (block_a.get ()), representative_a, amount_a, generate_work_a)); action_a (block); }); } @@ -1178,12 +1257,17 @@ rai::block_hash rai::wallet::send_sync (rai::account const & source_a, rai::acco void rai::wallet::send_async (rai::account const & source_a, rai::account const & account_a, rai::uint128_t const & amount_a, std::function )> const & action_a) { - node.background ([this, source_a, account_a, amount_a, action_a] () + send_async (source_a, account_a, amount_a, action_a, true); +} + +void rai::wallet::send_async (rai::account const & source_a, rai::account const & account_a, rai::uint128_t const & amount_a, std::function )> const & action_a, bool const & generate_work_a) +{ + node.background ([this, source_a, account_a, amount_a, action_a, generate_work_a] () { - this->node.wallets.queue_wallet_action (source_a, rai::wallets::high_priority, [this, source_a, account_a, amount_a, action_a] () + this->node.wallets.queue_wallet_action (source_a, rai::wallets::high_priority, [this, source_a, account_a, amount_a, action_a, generate_work_a] () { assert (!check_ownership (node.wallets, source_a)); - auto block (send_action (source_a, account_a, amount_a)); + auto block (send_action (source_a, account_a, amount_a, generate_work_a)); action_a (block); }); }); diff --git a/rai/node/wallet.hpp b/rai/node/wallet.hpp index 4a9bf54d..451fd793 100644 --- a/rai/node/wallet.hpp +++ b/rai/node/wallet.hpp @@ -149,36 +149,46 @@ class wallet : public std::enable_shared_from_this { public: std::shared_ptr change_action (rai::account const &, rai::account const &); - std::shared_ptr receive_action (rai::send_block const &, rai::account const &, rai::uint128_union const &); + std::shared_ptr change_action (rai::account const &, rai::account const &, bool const &); + std::shared_ptr receive_action (rai::send_block const &, rai::account const &, rai::uint128_union const &); + std::shared_ptr receive_action (rai::send_block const &, rai::account const &, rai::uint128_union const &, bool const &); std::shared_ptr send_action (rai::account const &, rai::account const &, rai::uint128_t const &); - wallet (bool &, rai::transaction &, rai::node &, std::string const &); - wallet (bool &, rai::transaction &, rai::node &, std::string const &, std::string const &); + std::shared_ptr send_action (rai::account const &, rai::account const &, rai::uint128_t const &, bool const &); + wallet (bool &, rai::transaction &, rai::node &, std::string const &); + wallet (bool &, rai::transaction &, rai::node &, std::string const &, std::string const &); void enter_initial_password (); - bool valid_password (); - bool enter_password (std::string const &); + bool valid_password (); + bool enter_password (std::string const &); rai::public_key insert_adhoc (rai::raw_key const &); + rai::public_key insert_adhoc (rai::raw_key const &, bool const &); rai::public_key insert_adhoc (MDB_txn *, rai::raw_key const &); + rai::public_key insert_adhoc (MDB_txn *, rai::raw_key const &, bool const &); rai::public_key deterministic_insert (MDB_txn *); + rai::public_key deterministic_insert (MDB_txn *, bool const &); rai::public_key deterministic_insert (); - bool exists (rai::public_key const &); + rai::public_key deterministic_insert (bool const &); + bool exists (rai::public_key const &); bool import (std::string const &, std::string const &); void serialize (std::string &); bool change_sync (rai::account const &, rai::account const &); void change_async (rai::account const &, rai::account const &, std::function )> const &); - bool receive_sync (std::shared_ptr , rai::account const &, rai::uint128_t const &); + void change_async (rai::account const &, rai::account const &, std::function )> const &, bool const &); + bool receive_sync (std::shared_ptr , rai::account const &, rai::uint128_t const &); void receive_async (std::shared_ptr , rai::account const &, rai::uint128_t const &, std::function )> const &); + void receive_async (std::shared_ptr , rai::account const &, rai::uint128_t const &, std::function )> const &, bool const &); rai::block_hash send_sync (rai::account const &, rai::account const &, rai::uint128_t const &); void send_async (rai::account const &, rai::account const &, rai::uint128_t const &, std::function )> const &); - void work_generate (rai::account const &, rai::block_hash const &); - void work_update (MDB_txn *, rai::account const &, rai::block_hash const &, uint64_t); - uint64_t work_fetch (MDB_txn *, rai::account const &, rai::block_hash const &); + void send_async (rai::account const &, rai::account const &, rai::uint128_t const &, std::function )> const &, bool const &); + void work_generate (rai::account const &, rai::block_hash const &); + void work_update (MDB_txn *, rai::account const &, rai::block_hash const &, uint64_t); + uint64_t work_fetch (MDB_txn *, rai::account const &, rai::block_hash const &); void work_ensure (MDB_txn *, rai::account const &); bool search_pending (); void init_free_accounts (MDB_txn *); std::unordered_set free_accounts; std::function lock_observer; - rai::wallet_store store; - rai::node & node; + rai::wallet_store store; + rai::node & node; }; // The wallets set is all the wallets a node controls. A node may contain multiple wallets independently encrypted and operated. class wallets @@ -187,8 +197,8 @@ public: wallets (bool &, rai::node &); std::shared_ptr open (rai::uint256_union const &); std::shared_ptr create (rai::uint256_union const &); - bool search_pending (rai::uint256_union const &); - void search_pending_all (); + bool search_pending (rai::uint256_union const &); + void search_pending_all (); void destroy (rai::uint256_union const &); void do_wallet_actions (rai::account const &); void queue_wallet_action (rai::account const &, rai::uint128_t const &, std::function const &);