diff --git a/nano/core_test/node.cpp b/nano/core_test/node.cpp index c5be65aa1..4f4962cdf 100644 --- a/nano/core_test/node.cpp +++ b/nano/core_test/node.cpp @@ -1,5 +1,6 @@ #include #include +#include #include #include @@ -428,7 +429,7 @@ TEST (logging, serialization) logging1.work_generation_time_value = !logging1.work_generation_time_value; logging1.log_to_cerr_value = !logging1.log_to_cerr_value; logging1.max_size = 10; - boost::property_tree::ptree tree; + nano::jsonconfig tree; logging1.serialize_json (tree); nano::logging logging2; logging2.init (path); @@ -460,7 +461,7 @@ TEST (logging, upgrade_v1_v2) logging1.init (path1); nano::logging logging2; logging2.init (path2); - boost::property_tree::ptree tree; + nano::jsonconfig tree; logging1.serialize_json (tree); tree.erase ("version"); tree.erase ("vote"); @@ -499,7 +500,7 @@ TEST (node_config, serialization) config1.callback_port = 10; config1.callback_target = "test"; config1.lmdb_max_dbs = 256; - boost::property_tree::ptree tree; + nano::jsonconfig tree; config1.serialize_json (tree); nano::logging logging2; logging2.init (path); @@ -541,27 +542,27 @@ TEST (node_config, v1_v2_upgrade) auto path (nano::unique_path ()); nano::logging logging1; logging1.init (path); - boost::property_tree::ptree tree; + nano::jsonconfig tree; tree.put ("peering_port", std::to_string (0)); tree.put ("packet_delay_microseconds", std::to_string (0)); tree.put ("bootstrap_fraction_numerator", std::to_string (0)); tree.put ("creation_rebroadcast", std::to_string (0)); tree.put ("rebroadcast_delay", std::to_string (0)); tree.put ("receive_minimum", nano::amount (0).to_string_dec ()); - boost::property_tree::ptree logging_l; + nano::jsonconfig logging_l; logging1.serialize_json (logging_l); - tree.add_child ("logging", logging_l); - boost::property_tree::ptree preconfigured_peers_l; - tree.add_child ("preconfigured_peers", preconfigured_peers_l); - boost::property_tree::ptree preconfigured_representatives_l; - tree.add_child ("preconfigured_representatives", preconfigured_representatives_l); + tree.put_child ("logging", logging_l); + nano::jsonconfig preconfigured_peers_l; + tree.put_child ("preconfigured_peers", preconfigured_peers_l); + nano::jsonconfig preconfigured_representatives_l; + tree.put_child ("preconfigured_representatives", preconfigured_representatives_l); bool upgraded (false); nano::node_config config1; config1.logging.init (path); - ASSERT_FALSE (tree.get_child_optional ("work_peers")); + ASSERT_FALSE (tree.get_optional_child ("work_peers")); config1.deserialize_json (upgraded, tree); ASSERT_TRUE (upgraded); - ASSERT_TRUE (!!tree.get_child_optional ("work_peers")); + ASSERT_TRUE (!!tree.get_optional_child ("work_peers")); } TEST (node_config, v2_v3_upgrade) @@ -569,7 +570,7 @@ TEST (node_config, v2_v3_upgrade) auto path (nano::unique_path ()); nano::logging logging1; logging1.init (path); - boost::property_tree::ptree tree; + nano::jsonconfig tree; tree.put ("peering_port", std::to_string (0)); tree.put ("packet_delay_microseconds", std::to_string (0)); tree.put ("bootstrap_fraction_numerator", std::to_string (0)); @@ -577,18 +578,16 @@ TEST (node_config, v2_v3_upgrade) tree.put ("rebroadcast_delay", std::to_string (0)); tree.put ("receive_minimum", nano::amount (0).to_string_dec ()); tree.put ("version", "2"); - boost::property_tree::ptree logging_l; + nano::jsonconfig logging_l; logging1.serialize_json (logging_l); - tree.add_child ("logging", logging_l); - boost::property_tree::ptree preconfigured_peers_l; - tree.add_child ("preconfigured_peers", preconfigured_peers_l); - boost::property_tree::ptree preconfigured_representatives_l; - boost::property_tree::ptree entry; - entry.put ("", "TR6ZJ4pdp6HC76xMRpVDny5x2s8AEbrhFue3NKVxYYdmKuTEib"); - preconfigured_representatives_l.push_back (std::make_pair ("", entry)); - tree.add_child ("preconfigured_representatives", preconfigured_representatives_l); - boost::property_tree::ptree work_peers_l; - tree.add_child ("work_peers", work_peers_l); + tree.put_child ("logging", logging_l); + nano::jsonconfig preconfigured_peers_l; + tree.put_child ("preconfigured_peers", preconfigured_peers_l); + nano::jsonconfig preconfigured_representatives_l; + preconfigured_representatives_l.push ("TR6ZJ4pdp6HC76xMRpVDny5x2s8AEbrhFue3NKVxYYdmKuTEib"); + tree.put_child ("preconfigured_representatives", preconfigured_representatives_l); + nano::jsonconfig work_peers_l; + tree.put_child ("work_peers", work_peers_l); bool upgraded (false); nano::node_config config1; config1.logging.init (path); diff --git a/nano/core_test/rpc.cpp b/nano/core_test/rpc.cpp index ab550b49f..fe0e957e0 100644 --- a/nano/core_test/rpc.cpp +++ b/nano/core_test/rpc.cpp @@ -6,6 +6,7 @@ #include #include #include +#include #include #include #include @@ -1578,7 +1579,7 @@ TEST (rpc_config, serialization) config1.enable_control = true; config1.frontier_request_limit = 8192; config1.chain_request_limit = 4096; - boost::property_tree::ptree tree; + nano::jsonconfig tree; config1.serialize_json (tree); nano::rpc_config config2; ASSERT_NE (config2.address, config1.address); diff --git a/nano/core_test/uint256_union.cpp b/nano/core_test/uint256_union.cpp index 5ddc498e4..d79db5ad3 100644 --- a/nano/core_test/uint256_union.cpp +++ b/nano/core_test/uint256_union.cpp @@ -1,6 +1,7 @@ #include #include +#include #include namespace @@ -427,24 +428,38 @@ TEST (uint256_union, operator_less_than) test_union_operator_less_than (); } +class json_initial_value_test +{ +public: + json_initial_value_test (std::string text_a) : + text (std::move (text_a)) + { + } + nano::error serialize_json (nano::jsonconfig & json) + { + json.put ("thing", text); + return json.get_error (); + } + std::string text; +}; + class json_upgrade_test { public: - bool deserialize_json (bool & upgraded, boost::property_tree::ptree & tree_a) + nano::error deserialize_json (bool & upgraded, nano::jsonconfig & json) { - auto error (false); - if (!tree_a.empty ()) + if (!json.empty ()) { - auto text_l (tree_a.get ("thing")); - if (text_l == "junktest") + auto text_l (json.get ("thing")); + if (text_l == "junktest" || text_l == "created") { upgraded = true; text_l = "changed"; - tree_a.put ("thing", text_l); + json.put ("thing", text_l); } if (text_l == "error") { - error = true; + json.get_error () = nano::error_common::generic; } text = text_l; } @@ -452,63 +467,41 @@ public: { upgraded = true; text = "created"; - tree_a.put ("thing", text); + json.put ("thing", text); } - return error; + return json.get_error (); } std::string text; }; -TEST (json, fetch_object) +/** Both create and upgrade via read_and_update() */ +TEST (json, create_and_upgrade) { - auto path1 (nano::unique_path ()); - std::fstream stream1; - nano::open_or_create (stream1, path1.string ()); - stream1 << "{ \"thing\": \"junktest\" }"; - stream1.close (); - nano::open_or_create (stream1, path1.string ()); + auto path (nano::unique_path ()); + nano::jsonconfig json; json_upgrade_test object1; - auto error1 (nano::fetch_object (object1, path1)); - ASSERT_FALSE (error1); - ASSERT_EQ ("changed", object1.text); - boost::property_tree::ptree tree1; - stream1.close (); - nano::open_or_create (stream1, path1.string ()); - boost::property_tree::read_json (stream1, tree1); - ASSERT_EQ ("changed", tree1.get ("thing")); - std::string string2 ("{ \"thing\": \"junktest2\" }"); - std::stringstream stream2 (string2); + ASSERT_FALSE (json.read_and_update (object1, path)); + ASSERT_EQ ("created", object1.text); + + nano::jsonconfig json2; json_upgrade_test object2; - auto error2 (nano::fetch_object (object2, stream2)); - ASSERT_FALSE (error2); - ASSERT_EQ ("junktest2", object2.text); - ASSERT_EQ ("{ \"thing\": \"junktest2\" }", string2); - std::string string3 ("{ \"thing\": \"error\" }"); - std::stringstream stream3 (string3); - json_upgrade_test object3; - auto error3 (nano::fetch_object (object3, stream3)); - ASSERT_TRUE (error3); - auto path2 (nano::unique_path ()); - std::fstream stream4; - nano::open_or_create (stream4, path2.string ()); - json_upgrade_test object4; - auto error4 (nano::fetch_object (object4, path2)); - ASSERT_FALSE (error4); - ASSERT_EQ ("created", object4.text); - boost::property_tree::ptree tree2; - stream4.close (); - nano::open_or_create (stream4, path2.string ()); - boost::property_tree::read_json (stream4, tree2); - ASSERT_EQ ("created", tree2.get ("thing")); + ASSERT_FALSE (json2.read_and_update (object2, path)); + ASSERT_EQ ("changed", object2.text); } -TEST (json, DISABLED_fetch_write_fail) +/** Create config manually, then upgrade via read_and_update() with multiple calls to test idempotence */ +TEST (json, upgrade_from_existing) { - std::string string4 (""); - std::stringstream stream4 (string4, std::ios_base::in); - json_upgrade_test object4; - auto error4 (nano::fetch_object (object4, stream4)); - ASSERT_TRUE (error4); + auto path (nano::unique_path ()); + nano::jsonconfig json; + json_initial_value_test junktest ("junktest"); + junktest.serialize_json (json); + json.write (path); + json_upgrade_test object1; + ASSERT_FALSE (json.read_and_update (object1, path)); + ASSERT_EQ ("changed", object1.text); + ASSERT_FALSE (json.read_and_update (object1, path)); + ASSERT_EQ ("changed", object1.text); } TEST (uint64_t, parse) diff --git a/nano/core_test/work_pool.cpp b/nano/core_test/work_pool.cpp index 5f2f63285..877e741eb 100644 --- a/nano/core_test/work_pool.cpp +++ b/nano/core_test/work_pool.cpp @@ -1,5 +1,6 @@ #include +#include #include #include @@ -88,7 +89,7 @@ TEST (work, opencl_config) config1.platform = 1; config1.device = 2; config1.threads = 3; - boost::property_tree::ptree tree; + nano::jsonconfig tree; config1.serialize_json (tree); nano::opencl_config config2; ASSERT_FALSE (config2.deserialize_json (tree)); diff --git a/nano/lib/CMakeLists.txt b/nano/lib/CMakeLists.txt index 5723c9649..a33b7e59e 100644 --- a/nano/lib/CMakeLists.txt +++ b/nano/lib/CMakeLists.txt @@ -22,6 +22,7 @@ add_library (nano_lib config.hpp interface.cpp interface.h + jsonconfig.hpp numbers.cpp numbers.hpp timer.hpp diff --git a/nano/lib/errors.cpp b/nano/lib/errors.cpp index 685fb1213..a714d7ac4 100644 --- a/nano/lib/errors.cpp +++ b/nano/lib/errors.cpp @@ -20,6 +20,8 @@ std::string nano::error_common_messages::message (int ev) const return "Missing signature"; case nano::error_common::missing_work: return "Missing work"; + case nano::error_common::exception: + return "Exception thrown"; case nano::error_common::account_exists: return "Account already exists"; case nano::error_common::account_not_found: @@ -66,6 +68,8 @@ std::string nano::error_common_messages::message (int ev) const return "Invalid port"; case nano::error_common::invalid_index: return "Invalid index"; + case nano::error_common::invalid_type_conversion: + return "Invalid type conversion"; case nano::error_common::invalid_work: return "Invalid work"; case nano::error_common::numeric_conversion: @@ -199,3 +203,18 @@ std::string nano::error_process_messages::message (int ev) const return "Invalid error code"; } + +std::string nano::error_config_messages::message (int ev) const +{ + switch (static_cast (ev)) + { + case nano::error_config::generic: + return "Unknown error"; + case nano::error_config::invalid_value: + return "Invalid configuration value"; + case nano::error_config::missing_value: + return "Missing value in configuration"; + } + + return "Invalid error code"; +} diff --git a/nano/lib/errors.hpp b/nano/lib/errors.hpp index 6f53b3199..c1a59c12c 100644 --- a/nano/lib/errors.hpp +++ b/nano/lib/errors.hpp @@ -1,5 +1,8 @@ #pragma once +#include +#include +#include #include #include #include @@ -10,24 +13,11 @@ using tl::make_unexpected; namespace nano { -/** Returns the error code if non-zero, otherwise the value */ -template -auto either (T && value, std::error_code ec) -> expected::type, std::error_code> -{ - if (ec) - { - return make_unexpected (ec); - } - else - { - return std::move (value); - } -} - /** Common error codes */ enum class error_common { generic = 1, + exception, account_not_found, account_not_found_wallet, account_exists, @@ -57,6 +47,7 @@ enum class error_common invalid_index, invalid_ip_address, invalid_port, + invalid_type_conversion, invalid_work, insufficient_balance, numeric_conversion, @@ -125,6 +116,29 @@ enum class error_process block_position, // This block cannot follow the previous block other }; + +/** config.json deserialization related errors */ +enum class error_config +{ + generic = 1, + invalid_value, + missing_value, +}; + +} // nano namespace + +/** Returns the error code if non-zero, otherwise the value */ +template +auto either (T && value, std::error_code ec) -> expected, std::error_code> +{ + if (ec) + { + return make_unexpected (ec); + } + else + { + return std::move (value); + } } // Convenience macro to implement the standard boilerplate for using std::error_code with enums @@ -172,3 +186,290 @@ REGISTER_ERROR_CODES (nano, error_common); REGISTER_ERROR_CODES (nano, error_blocks); REGISTER_ERROR_CODES (nano, error_rpc); REGISTER_ERROR_CODES (nano, error_process); +REGISTER_ERROR_CODES (nano, error_config); + +/* boost->std error_code bridge */ +namespace nano +{ +namespace error_conversion +{ + const std::error_category & generic_category (); +} +} + +namespace std +{ +template <> +struct is_error_code_enum +: public std::true_type +{ +}; + +inline std::error_code make_error_code (boost::system::errc::errc_t e) +{ + return std::error_code (static_cast (e), + ::nano::error_conversion::generic_category ()); +} +} +namespace nano +{ +namespace error_conversion +{ + namespace detail + { + class generic_category : public std::error_category + { + public: + virtual const char * name () const noexcept override + { + return boost::system::generic_category ().name (); + } + virtual std::string message (int value) const override + { + return boost::system::generic_category ().message (value); + } + }; + } + inline const std::error_category & generic_category () + { + static detail::generic_category instance; + return instance; + } + + inline std::error_code convert (const boost::system::error_code & error) + { + if (error.category () == boost::system::generic_category ()) + { + return std::error_code (error.value (), + nano::error_conversion::generic_category ()); + } + assert (false); + + return nano::error_common::invalid_type_conversion; + } +} +} + +namespace nano +{ +/** Adapter for std/boost::error_code, std::exception and bool flags to facilitate unified error handling */ +class error +{ +public: + error () = default; + error (nano::error const & error_a) = default; + error (nano::error && error_a) = default; + + error (std::error_code code_a) + { + code = code_a; + } + + error (std::string message_a) + { + code = nano::error_common::generic; + message = std::move (message_a); + } + + error (std::exception const & exception_a) + { + code = nano::error_common::exception; + message = exception_a.what (); + } + + error & operator= (nano::error const & err_a) + { + code = err_a.code; + message = err_a.message; + return *this; + } + + error & operator= (nano::error && err_a) + { + code = err_a.code; + message = std::move (err_a.message); + return *this; + } + + /** Assign error code */ + error & operator= (const std::error_code code_a) + { + code = code_a; + message.clear (); + return *this; + } + + /** Assign boost error code (as converted to std::error_code) */ + error & operator= (const boost::system::error_code & code_a) + { + code = nano::error_conversion::convert (code_a); + message.clear (); + return *this; + } + + /** Assign boost error code (as converted to std::error_code) */ + error & operator= (const boost::system::errc::errc_t & code_a) + { + code = nano::error_conversion::convert (boost::system::errc::make_error_code (code_a)); + message.clear (); + return *this; + } + + /** Set the error to nano::error_common::generic and the error message to \p message_a */ + error & operator= (const std::string message_a) + { + code = nano::error_common::generic; + message = std::move (message_a); + return *this; + } + + /** Set the error to nano::error_common::generic. */ + error & operator= (bool is_error) + { + if (is_error) + { + code = nano::error_common::generic; + } + message.clear (); + return *this; + } + + /** Sets the error to nano::error_common::exception and adopts the exception error message. */ + error & operator= (std::exception const & exception_a) + { + code = nano::error_common::exception; + message = exception_a.what (); + return *this; + } + + /** Return true if this#error_code equals the parameter */ + bool operator== (const std::error_code code_a) + { + return code == code_a; + } + + /** Return true if this#error_code equals the parameter */ + bool operator== (const boost::system::error_code code_a) + { + return code.value () == code_a.value (); + } + + /** Call the function iff the current error is zero */ + error & then (std::function next) + { + return code ? *this : next (); + } + + /** If the current error is one of the listed codes, reset the error code */ + template + error & accept (ErrorCode... err) + { + // Convert variadic arguments to std::error_code + auto codes = { std::error_code (err)... }; + if (std::any_of (codes.begin (), codes.end (), [this, &codes](auto & code_a) { return code == code_a; })) + { + code.clear (); + } + + return *this; + } + + /** Implicit error_code conversion */ + explicit operator std::error_code () const + { + return code; + } + + /** Implicit bool conversion; true if there's an error */ + explicit operator bool () const + { + return code.value () != 0; + } + + /** Implicit string conversion; returns the error message or an empty string. */ + explicit operator std::string () const + { + return get_message (); + } + + /** + * Get error message, or an empty string if there's no error. If a custom error message is set, + * that will be returned, otherwise the error_code#message() is returned. + */ + std::string get_message () const + { + std::string res = message; + if (code && res.empty ()) + { + res = code.message (); + } + return res; + } + + /** Set an error message, but only if the error code is already set */ + error & on_error (std::string message_a) + { + if (code) + { + message = std::move (message_a); + } + return *this; + } + + /** Set an error message if the current error code matches \p code_a */ + error & on_error (std::error_code code_a, std::string message_a) + { + if (code == code_a) + { + message = std::move (message_a); + } + return *this; + } + + /** Set an error message and an error code */ + error & set (std::string message_a, std::error_code code_a = nano::error_common::generic) + { + message = message_a; + code = code_a; + return *this; + } + + /** Set a custom error message. If the error code is not set, it will be set to nano::error_common::generic. */ + error & set_message (std::string message_a) + { + if (!code) + { + code = nano::error_common::generic; + } + message = std::move (message_a); + return *this; + } + + /** Clear an errors */ + error & clear () + { + code.clear (); + message.clear (); + return *this; + } + +private: + std::error_code code; + std::string message; +}; + +/** + * A type that manages a nano::error. + * The default return type is nano::error&, though shared_ptr is a good option in cases + * where shared error state is desirable. + */ +template +class error_aware +{ + static_assert (std::is_same::value || std::is_same>::value, "Must be nano::error& or shared_ptr"); + +public: + /** Returns the error object managed by this object */ + virtual RET_TYPE get_error () = 0; +}; +} diff --git a/nano/lib/jsonconfig.hpp b/nano/lib/jsonconfig.hpp new file mode 100644 index 000000000..61cdc2c3f --- /dev/null +++ b/nano/lib/jsonconfig.hpp @@ -0,0 +1,487 @@ +#pragma once + +#include +#include +#include +#include +#include +#include +#include + +namespace nano +{ +/** Type trait to determine if T is compatible with boost's lexical_cast */ +template +struct is_lexical_castable : std::integral_constant::value && (boost::has_right_shift, T>::value || boost::has_right_shift, T>::value))> +{ +}; + +/* Type descriptions are used to automatically construct configuration error messages */ +// clang-format off +template inline std::string type_desc (void) { return "an unknown type"; } +template <> inline std::string type_desc (void) { return "an integer between -128 and 127"; } +template <> inline std::string type_desc (void) { return "an integer between 0 and 255"; } +template <> inline std::string type_desc (void) { return "an integer between -32768 and 32767"; } +template <> inline std::string type_desc (void) { return "an integer between 0 and 65535"; } +template <> inline std::string type_desc (void) { return "a 32-bit signed integer"; } +template <> inline std::string type_desc (void) { return "a 32-bit unsigned integer"; } +template <> inline std::string type_desc (void) { return "a 64-bit signed integer"; } +template <> inline std::string type_desc (void) { return "a 64-bit unsigned integer"; } +template <> inline std::string type_desc (void) { return "a single precision floating point number"; } +template <> inline std::string type_desc (void) { return "a double precison floating point number"; } +template <> inline std::string type_desc (void) { return "a character"; } +template <> inline std::string type_desc (void) { return "a string"; } +template <> inline std::string type_desc (void) { return "a boolean"; } +template <> inline std::string type_desc (void) { return "an IP address"; } +// clang-format on + +/** Manages a node in a boost configuration tree. */ +class jsonconfig : public nano::error_aware<> +{ +public: + jsonconfig () + { + error = std::make_shared (); + } + + jsonconfig (boost::property_tree::ptree const & tree_a, std::shared_ptr error_a = nullptr) : + tree (tree_a), error (error_a) + { + if (!error) + { + error = std::make_shared (); + } + } + + /** + * Reads a json object from the stream and if it was changed, write the object back to the stream. + * @return nano::error&, including a descriptive error message if the config file is malformed. + */ + template + nano::error & read_and_update (T & object, boost::filesystem::path const & path_a) + { + std::fstream stream; + open_or_create (stream, path_a.string ()); + if (!stream.fail ()) + { + try + { + boost::property_tree::read_json (stream, tree); + } + catch (std::runtime_error const & ex) + { + auto pos (stream.tellg ()); + if (pos != std::streampos (0)) + { + *error = ex; + } + } + stream.close (); + if (!*error) + { + auto updated (false); + *error = object.deserialize_json (updated, *this); + if (!*error && updated) + { + stream.open (path_a.string (), std::ios_base::out | std::ios_base::trunc); + try + { + boost::property_tree::write_json (stream, tree); + } + catch (std::runtime_error const & ex) + { + *error = ex; + } + stream.close (); + } + } + } + return *error; + } + + void write (boost::filesystem::path const & path_a) + { + std::fstream stream; + open_or_create (stream, path_a.string ()); + write (stream); + } + + void write (std::ostream & stream_a) const + { + boost::property_tree::write_json (stream_a, tree); + } + + void read (std::istream & stream_a) + { + boost::property_tree::read_json (stream_a, tree); + } + + /** Open confiruation file, create if necessary */ + void open_or_create (std::fstream & stream_a, std::string const & path_a) + { + stream_a.open (path_a, std::ios_base::in); + if (stream_a.fail ()) + { + stream_a.open (path_a, std::ios_base::out); + } + stream_a.close (); + stream_a.open (path_a, std::ios_base::in | std::ios_base::out); + } + + /** Returns the boost property node managed by this instance */ + boost::property_tree::ptree const & get_tree () + { + return tree; + } + + /** Returns true if the property tree node is empty */ + bool empty () const + { + return tree.empty (); + } + + /** Optionally returns the given child node */ + jsonconfig & get_optional_child (std::string const & key_a, boost::optional & child_config) + { + auto child = tree.get_child_optional (key_a); + if (child) + { + child_config = jsonconfig (child.get (), error); + } + return *this; + } + + boost::optional get_optional_child (std::string const & key_a) + { + boost::optional child_config; + auto child = tree.get_child_optional (key_a); + if (child) + { + child_config = jsonconfig (child.get (), error); + } + return child_config; + } + + jsonconfig & get_required_child (std::string const & key_a, jsonconfig & child_config /*out*/) + { + auto child = tree.get_child_optional (key_a); + if (child) + { + child_config = jsonconfig (child.get (), error); + } + else if (!*error) + { + *error = nano::error_config::missing_value; + error->set_message ("Missing configuration node: " + key_a); + } + + return *this; + } + + jsonconfig get_required_child (std::string const & key_a) + { + jsonconfig child_config; + get_required_child (key_a, child_config); + return child_config; + } + + jsonconfig & put_child (std::string const & key_a, nano::jsonconfig & conf_a) + { + tree.add_child (key_a, conf_a.get_tree ()); + return *this; + } + + jsonconfig & replace_child (std::string const & key_a, nano::jsonconfig & conf_a) + { + conf_a.erase (key_a); + conf_a.put_child (key_a, conf_a); + return *this; + } + + /** Set value for the given key. Any existing value will be overwritten. */ + template + jsonconfig & put (std::string const & key, T const & value) + { + tree.put (key, value); + return *this; + } + + /** Push array element */ + template + jsonconfig & push (T const & value) + { + boost::property_tree::ptree entry; + entry.put ("", value); + tree.push_back (std::make_pair ("", entry)); + return *this; + } + + /** Iterate array entries */ + template + jsonconfig & array_entries (std::function callback) + { + for (auto & entry : tree) + { + callback (entry.second.get ("")); + } + return *this; + } + + /** Returns true if \p key_a is present */ + bool has_key (std::string const & key_a) + { + return tree.find (key_a) != tree.not_found (); + } + + /** Erase the property of given key */ + jsonconfig & erase (std::string const & key_a) + { + tree.erase (key_a); + return *this; + } + + /** Get optional, using \p default_value if \p key is missing. */ + template + jsonconfig & get_optional (std::string const & key, T & target, T default_value) + { + get_config (true, key, target, default_value); + return *this; + } + + /** + * Get optional value, using the current value of \p target as the default if \p key is missing. + * @return May return nano::error_config::invalid_value + */ + template + jsonconfig & get_optional (std::string const & key, T & target) + { + get_config (true, key, target, target); + return *this; + } + + /** Return a boost::optional for the given key */ + template + boost::optional get_optional (std::string const & key) + { + boost::optional res; + if (has_key (key)) + { + T target{}; + get_config (true, key, target, target); + res = target; + } + return res; + } + + /** Get value, using the current value of \p target as the default if \p key is missing. */ + template + jsonconfig & get (std::string const & key, T & target) + { + get_config (true, key, target, target); + return *this; + } + + /** + * Get value of required key + * @note May set nano::error_config::missing_value if \p key is missing, nano::error_config::invalid_value if value is invalid. + */ + template + T get (std::string const & key) + { + T target{}; + get_config (true, key, target, target); + return target; + } + + /** + * Get required value. + * @note May set nano::error_config::missing_value if \p key is missing, nano::error_config::invalid_value if value is invalid. + */ + template + jsonconfig & get_required (std::string const & key, T & target) + { + get_config (false, key, target); + return *this; + } + + /** Turn on or off automatic error message generation */ + void set_auto_error_message (bool auto_a) + { + auto_error_message = auto_a; + } + + nano::error & get_error () override + { + return *error; + } + +protected: + template + void construct_error_message (bool optional, std::string const & key) + { + if (auto_error_message && *error) + { + if (optional) + { + error->set_message (key + " is not " + type_desc ()); + } + else + { + error->set_message (key + " is required and must be " + type_desc ()); + } + } + } + + /** Set error if not already set. That is, first error remains until get_error().clear() is called. */ + template + void conditionally_set_error (V error_a, bool optional, std::string const & key) + { + if (!*error) + { + *error = error_a; + construct_error_message (optional, key); + } + } + template ::value>> + jsonconfig & get_config (bool optional, std::string key, T & target, T default_value = T ()) + { + try + { + auto val (tree.get (key)); + if (!boost::conversion::try_lexical_convert (val, target)) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + } + catch (boost::property_tree::ptree_bad_path const & ex) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; + } + + // boost's lexical cast doesn't handle (u)int8_t + template ::value>> + jsonconfig & get_config (bool optional, std::string key, uint8_t & target, uint8_t default_value = T ()) + { + int64_t tmp; + try + { + auto val (tree.get (key)); + if (!boost::conversion::try_lexical_convert (val, tmp) || tmp < 0 || tmp > 255) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + else + { + target = static_cast (tmp); + } + } + catch (boost::property_tree::ptree_bad_path const & ex) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; + } + + template ::value>> + jsonconfig & get_config (bool optional, std::string key, bool & target, bool default_value = false) + { + auto bool_conv = [this, &target, &key, optional](std::string val) { + if (val == "true") + { + target = true; + } + else if (val == "false") + { + target = false; + } + else if (!*error) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + }; + try + { + auto val (tree.get (key)); + bool_conv (val); + } + catch (boost::property_tree::ptree_bad_path const & ex) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + catch (std::runtime_error & ex) + { + conditionally_set_error (ex, optional, key); + } + return *this; + } + + template ::value>> + jsonconfig & get_config (bool optional, std::string key, boost::asio::ip::address_v6 & target, boost::asio::ip::address_v6 default_value = T ()) + { + try + { + auto address_l (tree.get (key)); + boost::system::error_code bec; + target = boost::asio::ip::address_v6::from_string (address_l, bec); + if (bec) + { + conditionally_set_error (nano::error_config::invalid_value, optional, key); + } + } + catch (boost::property_tree::ptree_bad_path const & ex) + { + if (!optional) + { + conditionally_set_error (nano::error_config::missing_value, optional, key); + } + else + { + target = default_value; + } + } + return *this; + } + +private: + /** The property node being managed */ + boost::property_tree::ptree tree; + + /** If set, automatically construct error messages based on parameters and type information. */ + bool auto_error_message{ true }; + + /** We're a nano::error_aware type. Child nodes share the error state. */ + std::shared_ptr error; +}; +} diff --git a/nano/nano_node/daemon.cpp b/nano/nano_node/daemon.cpp index f275c51f0..79e3ab502 100644 --- a/nano/nano_node/daemon.cpp +++ b/nano/nano_node/daemon.cpp @@ -1,3 +1,4 @@ +#include #include #include @@ -12,86 +13,104 @@ opencl_enable (false) { } -void nano_daemon::daemon_config::serialize_json (boost::property_tree::ptree & tree_a) +nano::error nano_daemon::daemon_config::serialize_json (nano::jsonconfig & json) { - tree_a.put ("version", std::to_string (json_version)); - tree_a.put ("rpc_enable", rpc_enable); - boost::property_tree::ptree rpc_l; + json.put ("version", json_version ()); + json.put ("rpc_enable", rpc_enable); + + nano::jsonconfig rpc_l; rpc.serialize_json (rpc_l); - tree_a.add_child ("rpc", rpc_l); - boost::property_tree::ptree node_l; + json.put_child ("rpc", rpc_l); + + nano::jsonconfig node_l; node.serialize_json (node_l); - tree_a.add_child ("node", node_l); - tree_a.put ("opencl_enable", opencl_enable); - boost::property_tree::ptree opencl_l; + nano::jsonconfig node (node_l); + json.put_child ("node", node); + + json.put ("opencl_enable", opencl_enable); + nano::jsonconfig opencl_l; opencl.serialize_json (opencl_l); - tree_a.add_child ("opencl", opencl_l); + json.put_child ("opencl", opencl_l); + return json.get_error (); } -bool nano_daemon::daemon_config::deserialize_json (bool & upgraded_a, boost::property_tree::ptree & tree_a) +nano::error nano_daemon::daemon_config::deserialize_json (bool & upgraded_a, nano::jsonconfig & json) { - auto error (false); try { - if (!tree_a.empty ()) + if (!json.empty ()) { - auto version_l (tree_a.get_optional ("version")); - if (!version_l) + int version_l; + json.get_optional ("version", version_l); + upgraded_a |= upgrade_json (version_l, json); + + json.get_optional ("rpc_enable", rpc_enable); + nano::jsonconfig rpc_l; + json.get_required_child ("rpc", rpc_l); + + if (!rpc.deserialize_json (rpc_l)) { - tree_a.put ("version", "1"); - version_l = "1"; + nano::jsonconfig node_l; + json.get_required_child ("node", node_l); + if (!json.get_error ()) + { + node.deserialize_json (upgraded_a, node_l); + } + } + if (!json.get_error ()) + { + json.get_required ("opencl_enable", opencl_enable); + nano::jsonconfig opencl_l; + json.get_required_child ("opencl", opencl_l); + if (!json.get_error ()) + { + opencl.deserialize_json (opencl_l); + } } - upgraded_a |= upgrade_json (std::stoull (version_l.get ()), tree_a); - rpc_enable = tree_a.get ("rpc_enable"); - auto rpc_l (tree_a.get_child ("rpc")); - error |= rpc.deserialize_json (rpc_l); - auto & node_l (tree_a.get_child ("node")); - error |= node.deserialize_json (upgraded_a, node_l); - opencl_enable = tree_a.get ("opencl_enable"); - auto & opencl_l (tree_a.get_child ("opencl")); - error |= opencl.deserialize_json (opencl_l); } else { upgraded_a = true; - serialize_json (tree_a); + serialize_json (json); } } - catch (std::runtime_error const &) + catch (std::runtime_error const & ex) { - error = true; + json.get_error () = ex; } - return error; + return json.get_error (); } -bool nano_daemon::daemon_config::upgrade_json (unsigned version_a, boost::property_tree::ptree & tree_a) +bool nano_daemon::daemon_config::upgrade_json (unsigned version_a, nano::jsonconfig & json) { - tree_a.put ("version", std::to_string (json_version)); - auto result (false); + json.put ("version", json_version ()); + auto upgraded_l (false); switch (version_a) { case 1: { - auto opencl_enable_l (tree_a.get_optional ("opencl_enable")); + bool opencl_enable_l; + json.get_optional ("opencl_enable", opencl_enable_l); if (!opencl_enable_l) { - tree_a.put ("opencl_enable", "false"); + json.put ("opencl_enable", false); } - auto opencl_l (tree_a.get_child_optional ("opencl")); + boost::optional opencl_l; + json.get_optional_child ("opencl", opencl_l); if (!opencl_l) { - boost::property_tree::ptree opencl_l; + nano::jsonconfig opencl_l; opencl.serialize_json (opencl_l); - tree_a.put_child ("opencl", opencl_l); + json.put_child ("opencl", opencl_l); } - result = true; + upgraded_l = true; } case 2: break; default: throw std::runtime_error ("Unknown daemon_config version"); } - return result; + return upgraded_l; } void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano::node_flags const & flags) @@ -102,7 +121,8 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: nano::set_secure_perm_directory (data_path, error_chmod); auto config_path ((data_path / "config.json")); std::unique_ptr runner; - auto error (nano::fetch_object (config, config_path)); + nano::jsonconfig json; + auto error (json.read_and_update (config, config_path)); nano::set_secure_perm_file (config_path, error_chmod); if (!error) { @@ -142,6 +162,6 @@ void nano_daemon::daemon::run (boost::filesystem::path const & data_path, nano:: } else { - std::cerr << "Error deserializing config\n"; + std::cerr << "Error deserializing config: " << error.get_message () << std::endl; } } diff --git a/nano/nano_node/daemon.hpp b/nano/nano_node/daemon.hpp index f9969b76f..5ee10fb91 100644 --- a/nano/nano_node/daemon.hpp +++ b/nano/nano_node/daemon.hpp @@ -1,3 +1,4 @@ +#include #include #include @@ -12,14 +13,22 @@ class daemon_config { public: daemon_config (); - bool deserialize_json (bool &, boost::property_tree::ptree &); - void serialize_json (boost::property_tree::ptree &); - bool upgrade_json (unsigned, boost::property_tree::ptree &); + nano::error deserialize_json (bool &, nano::jsonconfig &); + nano::error serialize_json (nano::jsonconfig &); + /** + * Returns true if an upgrade occurred + * @param version The version to upgrade to. + * @param config Configuration to upgrade. + */ + bool upgrade_json (unsigned version, nano::jsonconfig & config); bool rpc_enable; nano::rpc_config rpc; nano::node_config node; bool opencl_enable; nano::opencl_config opencl; - static constexpr int json_version = 2; + int json_version () const + { + return 2; + } }; } diff --git a/nano/nano_wallet/entry.cpp b/nano/nano_wallet/entry.cpp index 51145a65e..f67251727 100644 --- a/nano/nano_wallet/entry.cpp +++ b/nano/nano_wallet/entry.cpp @@ -1,3 +1,5 @@ +#include +#include #include #include #include @@ -21,135 +23,139 @@ public: nano::random_pool.GenerateBlock (wallet.bytes.data (), wallet.bytes.size ()); assert (!wallet.is_zero ()); } - bool upgrade_json (unsigned version_a, boost::property_tree::ptree & tree_a) + bool upgrade_json (unsigned version_a, nano::jsonconfig & json) { - tree_a.put ("version", std::to_string (json_version)); - auto result (false); + json.put ("version", json_version ()); + auto upgraded (false); switch (version_a) { case 1: { nano::account account; - account.decode_account (tree_a.get ("account")); - tree_a.erase ("account"); - tree_a.put ("account", account.to_account ()); - tree_a.erase ("version"); - result = true; + account.decode_account (json.get ("account")); + json.erase ("account"); + json.put ("account", account.to_account ()); + json.erase ("version"); + upgraded = true; } case 2: { - boost::property_tree::ptree rpc_l; + nano::jsonconfig rpc_l; rpc.serialize_json (rpc_l); - tree_a.put ("rpc_enable", "false"); - tree_a.put_child ("rpc", rpc_l); - tree_a.erase ("version"); - result = true; + json.put ("rpc_enable", "false"); + json.put_child ("rpc", rpc_l); + json.erase ("version"); + upgraded = true; } case 3: { - auto opencl_enable_l (tree_a.get_optional ("opencl_enable")); + auto opencl_enable_l (json.get_optional ("opencl_enable")); if (!opencl_enable_l) { - tree_a.put ("opencl_enable", "false"); + json.put ("opencl_enable", "false"); } - auto opencl_l (tree_a.get_child_optional ("opencl")); + auto opencl_l (json.get_optional_child ("opencl")); if (!opencl_l) { - boost::property_tree::ptree opencl_l; + nano::jsonconfig opencl_l; opencl.serialize_json (opencl_l); - tree_a.put_child ("opencl", opencl_l); + json.put_child ("opencl", opencl_l); } - result = true; + upgraded = true; } case 4: break; default: throw std::runtime_error ("Unknown qt_wallet_config version"); } - return result; + return upgraded; } - bool deserialize_json (bool & upgraded_a, boost::property_tree::ptree & tree_a) + + nano::error deserialize_json (bool & upgraded_a, nano::jsonconfig & json) { - auto error (false); - if (!tree_a.empty ()) + if (!json.empty ()) { - auto version_l (tree_a.get_optional ("version")); + auto version_l (json.get_optional ("version")); if (!version_l) { - tree_a.put ("version", "1"); - version_l = "1"; + version_l = 1; + json.put ("version", version_l.get ()); upgraded_a = true; } - upgraded_a |= upgrade_json (std::stoull (version_l.get ()), tree_a); - auto wallet_l (tree_a.get ("wallet")); - auto account_l (tree_a.get ("account")); - auto & node_l (tree_a.get_child ("node")); - rpc_enable = tree_a.get ("rpc_enable"); - auto & rpc_l (tree_a.get_child ("rpc")); - opencl_enable = tree_a.get ("opencl_enable"); - auto & opencl_l (tree_a.get_child ("opencl")); - try + upgraded_a |= upgrade_json (version_l.get (), json); + auto wallet_l (json.get ("wallet")); + auto account_l (json.get ("account")); + auto node_l (json.get_required_child ("node")); + rpc_enable = json.get ("rpc_enable"); + auto rpc_l (json.get_required_child ("rpc")); + opencl_enable = json.get ("opencl_enable"); + auto opencl_l (json.get_required_child ("opencl")); + + if (wallet.decode_hex (wallet_l)) { - error |= wallet.decode_hex (wallet_l); - error |= account.decode_account (account_l); - error |= node.deserialize_json (upgraded_a, node_l); - error |= rpc.deserialize_json (rpc_l); - error |= opencl.deserialize_json (opencl_l); - if (wallet.is_zero ()) - { - nano::random_pool.GenerateBlock (wallet.bytes.data (), wallet.bytes.size ()); - upgraded_a = true; - } + json.get_error ().set ("Invalid wallet id. Did you open a node daemon config?"); } - catch (std::logic_error const &) + else if (account.decode_account (account_l)) { - error = true; + json.get_error ().set ("Invalid account"); + } + node.deserialize_json (upgraded_a, node_l); + rpc.deserialize_json (rpc_l); + opencl.deserialize_json (opencl_l); + if (wallet.is_zero ()) + { + nano::random_pool.GenerateBlock (wallet.bytes.data (), wallet.bytes.size ()); + upgraded_a = true; } } else { - serialize_json (tree_a); + serialize_json (json); upgraded_a = true; } - return error; + return json.get_error (); } - void serialize_json (boost::property_tree::ptree & tree_a) + + void serialize_json (nano::jsonconfig & json) { std::string wallet_string; wallet.encode_hex (wallet_string); - tree_a.put ("version", std::to_string (json_version)); - tree_a.put ("wallet", wallet_string); - tree_a.put ("account", account.to_account ()); - boost::property_tree::ptree node_l; + json.put ("version", json_version ()); + json.put ("wallet", wallet_string); + json.put ("account", account.to_account ()); + nano::jsonconfig node_l; node.enable_voting = false; node.bootstrap_connections_max = 4; node.serialize_json (node_l); - tree_a.add_child ("node", node_l); - boost::property_tree::ptree rpc_l; + json.put_child ("node", node_l); + nano::jsonconfig rpc_l; rpc.serialize_json (rpc_l); - tree_a.add_child ("rpc", rpc_l); - tree_a.put ("rpc_enable", rpc_enable); - tree_a.put ("opencl_enable", opencl_enable); - boost::property_tree::ptree opencl_l; + json.put_child ("rpc", rpc_l); + json.put ("rpc_enable", rpc_enable); + json.put ("opencl_enable", opencl_enable); + nano::jsonconfig opencl_l; opencl.serialize_json (opencl_l); - tree_a.add_child ("opencl", opencl_l); + json.put_child ("opencl", opencl_l); } + bool serialize_json_stream (std::ostream & stream_a) { auto result (false); stream_a.seekp (0); try { - boost::property_tree::ptree tree; - serialize_json (tree); - boost::property_tree::write_json (stream_a, tree); + nano::jsonconfig json; + serialize_json (json); + json.write (stream_a); } - catch (std::runtime_error const &) + catch (std::runtime_error const & ex) { + std::cerr << ex.what () << std::endl; result = true; } return result; } + nano::uint256_union wallet; nano::account account; nano::node_config node; @@ -157,7 +163,10 @@ public: nano::rpc_config rpc; bool opencl_enable; nano::opencl_config opencl; - static constexpr int json_version = 4; + int json_version () const + { + return 4; + } }; namespace @@ -169,19 +178,23 @@ void show_error (std::string const & message_a) message.show (); message.exec (); } -bool update_config (qt_wallet_config & config_a, boost::filesystem::path const & config_path_a, std::fstream & config_file_a) +bool update_config (qt_wallet_config & config_a, boost::filesystem::path const & config_path_a) { auto account (config_a.account); auto wallet (config_a.wallet); auto error (false); - if (!nano::fetch_object (config_a, config_path_a)) + nano::jsonconfig config; + if (!config.read_and_update (config_a, config_path_a)) { if (account != config_a.account || wallet != config_a.wallet) { config_a.account = account; config_a.wallet = wallet; - config_file_a.open (config_path_a.string (), std::ios_base::out | std::ios_base::trunc); - error = config_a.serialize_json_stream (config_file_a); + + // Update json file with new account and/or wallet values + std::fstream config_file; + config_file.open (config_path_a.string (), std::ios_base::out | std::ios_base::trunc); + error = config_a.serialize_json_stream (config_file); } } return error; @@ -203,9 +216,8 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost qt_wallet_config config (data_path); auto config_path ((data_path / "config.json")); int result (0); - std::fstream config_file; - auto error (nano::fetch_object (config, config_path)); - config_file.close (); + nano::jsonconfig json; + auto error (json.read_and_update (config, config_path)); nano::set_secure_perm_file (config_path, error_chmod); if (!error) { @@ -253,7 +265,7 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost } } assert (wallet->exists (config.account)); - update_config (config, config_path, config_file); + update_config (config, config_path); node->start (); std::unique_ptr rpc = get_rpc (io_ctx, *node, config.rpc); if (rpc && config.rpc_enable) @@ -276,13 +288,15 @@ int run_wallet (QApplication & application, int argc, char * const * argv, boost } else { + splash->hide (); show_error ("Error initializing node"); } - update_config (config, config_path, config_file); + update_config (config, config_path); } else { - show_error ("Error deserializing config"); + splash->hide (); + show_error ("Error deserializing config: " + json.get_error ().get_message ()); } return result; } diff --git a/nano/node/logging.cpp b/nano/node/logging.cpp index 00e1bf4ca..18b5a02b4 100644 --- a/nano/node/logging.cpp +++ b/nano/node/logging.cpp @@ -2,6 +2,7 @@ #include #include #include +#include #include nano::logging::logging () : @@ -42,104 +43,106 @@ void nano::logging::init (boost::filesystem::path const & application_path_a) } } -void nano::logging::serialize_json (boost::property_tree::ptree & tree_a) const +nano::error nano::logging::serialize_json (nano::jsonconfig & json) const { - tree_a.put ("version", std::to_string (json_version)); - tree_a.put ("ledger", ledger_logging_value); - tree_a.put ("ledger_duplicate", ledger_duplicate_logging_value); - tree_a.put ("vote", vote_logging_value); - tree_a.put ("network", network_logging_value); - tree_a.put ("network_message", network_message_logging_value); - tree_a.put ("network_publish", network_publish_logging_value); - tree_a.put ("network_packet", network_packet_logging_value); - tree_a.put ("network_keepalive", network_keepalive_logging_value); - tree_a.put ("network_node_id_handshake", network_node_id_handshake_logging_value); - tree_a.put ("node_lifetime_tracing", node_lifetime_tracing_value); - tree_a.put ("insufficient_work", insufficient_work_logging_value); - tree_a.put ("log_rpc", log_rpc_value); - tree_a.put ("bulk_pull", bulk_pull_logging_value); - tree_a.put ("work_generation_time", work_generation_time_value); - tree_a.put ("upnp_details", upnp_details_logging_value); - tree_a.put ("timing", timing_logging_value); - tree_a.put ("log_to_cerr", log_to_cerr_value); - tree_a.put ("max_size", max_size); - tree_a.put ("rotation_size", rotation_size); - tree_a.put ("flush", flush); + json.put ("version", json_version ()); + json.put ("ledger", ledger_logging_value); + json.put ("ledger_duplicate", ledger_duplicate_logging_value); + json.put ("vote", vote_logging_value); + json.put ("network", network_logging_value); + json.put ("network_message", network_message_logging_value); + json.put ("network_publish", network_publish_logging_value); + json.put ("network_packet", network_packet_logging_value); + json.put ("network_keepalive", network_keepalive_logging_value); + json.put ("network_node_id_handshake", network_node_id_handshake_logging_value); + json.put ("node_lifetime_tracing", node_lifetime_tracing_value); + json.put ("insufficient_work", insufficient_work_logging_value); + json.put ("log_rpc", log_rpc_value); + json.put ("bulk_pull", bulk_pull_logging_value); + json.put ("work_generation_time", work_generation_time_value); + json.put ("upnp_details", upnp_details_logging_value); + json.put ("timing", timing_logging_value); + json.put ("log_to_cerr", log_to_cerr_value); + json.put ("max_size", max_size); + json.put ("rotation_size", rotation_size); + json.put ("flush", flush); + return json.get_error (); } -bool nano::logging::upgrade_json (unsigned version_a, boost::property_tree::ptree & tree_a) +bool nano::logging::upgrade_json (unsigned version_a, nano::jsonconfig & json) { - tree_a.put ("version", std::to_string (json_version)); - auto result (false); + json.put ("version", json_version ()); + auto upgraded_l (false); switch (version_a) { case 1: - tree_a.put ("vote", vote_logging_value); - result = true; + json.put ("vote", vote_logging_value); + upgraded_l = true; case 2: - tree_a.put ("rotation_size", "4194304"); - tree_a.put ("flush", "true"); - result = true; + json.put ("rotation_size", rotation_size); + json.put ("flush", true); + upgraded_l = true; case 3: - tree_a.put ("network_node_id_handshake", "false"); - result = true; + json.put ("network_node_id_handshake", false); + upgraded_l = true; case 4: - tree_a.put ("upnp_details", "false"); - tree_a.put ("timing", "false"); - result = true; + json.put ("upnp_details", "false"); + json.put ("timing", "false"); + upgraded_l = true; case 5: break; default: throw std::runtime_error ("Unknown logging_config version"); break; } - return result; + return upgraded_l; } -bool nano::logging::deserialize_json (bool & upgraded_a, boost::property_tree::ptree & tree_a) +nano::error nano::logging::deserialize_json (bool & upgraded_a, nano::jsonconfig & json) { - auto result (false); - try + int version_l; + if (!json.has_key ("version")) { - auto version_l (tree_a.get_optional ("version")); - if (!version_l) + version_l = 1; + json.put ("version", version_l); + + boost::optional work_peers_l; + json.get_optional_child ("work_peers", work_peers_l); + if (!work_peers_l) { - tree_a.put ("version", "1"); - version_l = "1"; - auto work_peers_l (tree_a.get_child_optional ("work_peers")); - if (!work_peers_l) - { - tree_a.add_child ("work_peers", boost::property_tree::ptree ()); - } - upgraded_a = true; + nano::jsonconfig peers; + json.put_child ("work_peers", peers); } - upgraded_a |= upgrade_json (std::stoull (version_l.get ()), tree_a); - ledger_logging_value = tree_a.get ("ledger"); - ledger_duplicate_logging_value = tree_a.get ("ledger_duplicate"); - vote_logging_value = tree_a.get ("vote"); - network_logging_value = tree_a.get ("network"); - network_message_logging_value = tree_a.get ("network_message"); - network_publish_logging_value = tree_a.get ("network_publish"); - network_packet_logging_value = tree_a.get ("network_packet"); - network_keepalive_logging_value = tree_a.get ("network_keepalive"); - network_node_id_handshake_logging_value = tree_a.get ("network_node_id_handshake"); - node_lifetime_tracing_value = tree_a.get ("node_lifetime_tracing"); - insufficient_work_logging_value = tree_a.get ("insufficient_work"); - log_rpc_value = tree_a.get ("log_rpc"); - bulk_pull_logging_value = tree_a.get ("bulk_pull"); - work_generation_time_value = tree_a.get ("work_generation_time"); - upnp_details_logging_value = tree_a.get ("upnp_details"); - timing_logging_value = tree_a.get ("timing"); - log_to_cerr_value = tree_a.get ("log_to_cerr"); - max_size = tree_a.get ("max_size"); - rotation_size = tree_a.get ("rotation_size", 4194304); - flush = tree_a.get ("flush", true); + upgraded_a = true; } - catch (std::runtime_error const &) + else { - result = true; + json.get_required ("version", version_l); } - return result; + + upgraded_a |= upgrade_json (version_l, json); + json.get ("ledger", ledger_logging_value); + json.get ("ledger_duplicate", ledger_duplicate_logging_value); + json.get ("vote", vote_logging_value); + json.get ("network", network_logging_value); + json.get ("network_message", network_message_logging_value); + json.get ("network_publish", network_publish_logging_value); + json.get ("network_packet", network_packet_logging_value); + json.get ("network_keepalive", network_keepalive_logging_value); + json.get ("network_node_id_handshake", network_node_id_handshake_logging_value); + json.get ("node_lifetime_tracing", node_lifetime_tracing_value); + json.get ("insufficient_work", insufficient_work_logging_value); + json.get ("log_rpc", log_rpc_value); + json.get ("bulk_pull", bulk_pull_logging_value); + json.get ("work_generation_time", work_generation_time_value); + json.get ("upnp_details", upnp_details_logging_value); + json.get ("timing", timing_logging_value); + json.get ("log_to_cerr", log_to_cerr_value); + json.get ("flush", flush); + json.get ("max_size", max_size); + json.get ("rotation_size", rotation_size); + + return json.get_error (); } bool nano::logging::ledger_logging () const diff --git a/nano/node/logging.hpp b/nano/node/logging.hpp index ef6899b53..5149b0150 100644 --- a/nano/node/logging.hpp +++ b/nano/node/logging.hpp @@ -3,8 +3,9 @@ #include #include #include -#include #include +#include +#include #define FATAL_LOG_PREFIX "FATAL ERROR: " @@ -14,9 +15,9 @@ class logging { public: logging (); - void serialize_json (boost::property_tree::ptree &) const; - bool deserialize_json (bool &, boost::property_tree::ptree &); - bool upgrade_json (unsigned, boost::property_tree::ptree &); + nano::error serialize_json (nano::jsonconfig &) const; + nano::error deserialize_json (bool &, nano::jsonconfig &); + bool upgrade_json (unsigned, nano::jsonconfig &); bool ledger_logging () const; bool ledger_duplicate_logging () const; bool vote_logging () const; @@ -58,6 +59,9 @@ public: uintmax_t max_size; uintmax_t rotation_size; boost::log::sources::logger_mt log; - static constexpr int json_version = 5; + int json_version () const + { + return 5; + } }; } diff --git a/nano/node/nodeconfig.cpp b/nano/node/nodeconfig.cpp index 4b70c8d22..e29b50388 100644 --- a/nano/node/nodeconfig.cpp +++ b/nano/node/nodeconfig.cpp @@ -1,3 +1,4 @@ +#include #include // NOTE: to reduce compile times, this include can be replaced by more narrow includes // once nano::network is factored out of node.{c|h}pp @@ -61,262 +62,270 @@ block_processor_batch_max_time (std::chrono::milliseconds (5000)) } } -void nano::node_config::serialize_json (boost::property_tree::ptree & tree_a) const +nano::error nano::node_config::serialize_json (nano::jsonconfig & json) const { - tree_a.put ("version", std::to_string (json_version)); - tree_a.put ("peering_port", std::to_string (peering_port)); - tree_a.put ("bootstrap_fraction_numerator", std::to_string (bootstrap_fraction_numerator)); - tree_a.put ("receive_minimum", receive_minimum.to_string_dec ()); - boost::property_tree::ptree logging_l; + json.put ("version", json_version ()); + json.put ("peering_port", peering_port); + json.put ("bootstrap_fraction_numerator", bootstrap_fraction_numerator); + json.put ("receive_minimum", receive_minimum.to_string_dec ()); + + nano::jsonconfig logging_l; logging.serialize_json (logging_l); - tree_a.add_child ("logging", logging_l); - boost::property_tree::ptree work_peers_l; + json.put_child ("logging", logging_l); + + nano::jsonconfig work_peers_l; for (auto i (work_peers.begin ()), n (work_peers.end ()); i != n; ++i) { - boost::property_tree::ptree entry; - entry.put ("", boost::str (boost::format ("%1%:%2%") % i->first % i->second)); - work_peers_l.push_back (std::make_pair ("", entry)); + work_peers_l.push (boost::str (boost::format ("%1%:%2%") % i->first % i->second)); } - tree_a.add_child ("work_peers", work_peers_l); - boost::property_tree::ptree preconfigured_peers_l; + json.put_child ("work_peers", work_peers_l); + nano::jsonconfig preconfigured_peers_l; for (auto i (preconfigured_peers.begin ()), n (preconfigured_peers.end ()); i != n; ++i) { - boost::property_tree::ptree entry; - entry.put ("", *i); - preconfigured_peers_l.push_back (std::make_pair ("", entry)); + preconfigured_peers_l.push (*i); } - tree_a.add_child ("preconfigured_peers", preconfigured_peers_l); - boost::property_tree::ptree preconfigured_representatives_l; + json.put_child ("preconfigured_peers", preconfigured_peers_l); + + nano::jsonconfig preconfigured_representatives_l; for (auto i (preconfigured_representatives.begin ()), n (preconfigured_representatives.end ()); i != n; ++i) { - boost::property_tree::ptree entry; - entry.put ("", i->to_account ()); - preconfigured_representatives_l.push_back (std::make_pair ("", entry)); + preconfigured_representatives_l.push (i->to_account ()); } - tree_a.add_child ("preconfigured_representatives", preconfigured_representatives_l); - tree_a.put ("online_weight_minimum", online_weight_minimum.to_string_dec ()); - tree_a.put ("online_weight_quorum", std::to_string (online_weight_quorum)); - tree_a.put ("password_fanout", std::to_string (password_fanout)); - tree_a.put ("io_threads", std::to_string (io_threads)); - tree_a.put ("network_threads", std::to_string (network_threads)); - tree_a.put ("work_threads", std::to_string (work_threads)); - tree_a.put ("enable_voting", enable_voting); - tree_a.put ("bootstrap_connections", bootstrap_connections); - tree_a.put ("bootstrap_connections_max", bootstrap_connections_max); - tree_a.put ("callback_address", callback_address); - tree_a.put ("callback_port", std::to_string (callback_port)); - tree_a.put ("callback_target", callback_target); - tree_a.put ("lmdb_max_dbs", lmdb_max_dbs); - tree_a.put ("block_processor_batch_max_time", block_processor_batch_max_time.count ()); - tree_a.put ("allow_local_peers", allow_local_peers); + json.put_child ("preconfigured_representatives", preconfigured_representatives_l); + + json.put ("online_weight_minimum", online_weight_minimum.to_string_dec ()); + json.put ("online_weight_quorum", online_weight_quorum); + json.put ("password_fanout", password_fanout); + json.put ("io_threads", io_threads); + json.put ("network_threads", network_threads); + json.put ("work_threads", work_threads); + json.put ("enable_voting", enable_voting); + json.put ("bootstrap_connections", bootstrap_connections); + json.put ("bootstrap_connections_max", bootstrap_connections_max); + json.put ("callback_address", callback_address); + json.put ("callback_port", callback_port); + json.put ("callback_target", callback_target); + json.put ("lmdb_max_dbs", lmdb_max_dbs); + json.put ("block_processor_batch_max_time", block_processor_batch_max_time.count ()); + json.put ("allow_local_peers", allow_local_peers); + return json.get_error (); } -bool nano::node_config::upgrade_json (unsigned version_a, boost::property_tree::ptree & tree_a) +bool nano::node_config::upgrade_json (unsigned version_a, nano::jsonconfig & json) { - tree_a.put ("version", std::to_string (json_version)); - auto result (false); + json.put ("version", json_version ()); + auto upgraded (false); switch (version_a) { case 1: { - auto reps_l (tree_a.get_child ("preconfigured_representatives")); - boost::property_tree::ptree reps; - for (auto i (reps_l.begin ()), n (reps_l.end ()); i != n; ++i) - { + nano::jsonconfig reps_l; + json.get_required_child ("preconfigured_representatives", reps_l); + nano::jsonconfig reps; + reps_l.array_entries ([&reps](std::string entry) { nano::uint256_union account; - account.decode_account (i->second.get ("")); - boost::property_tree::ptree entry; - entry.put ("", account.to_account ()); - reps.push_back (std::make_pair ("", entry)); - } - tree_a.erase ("preconfigured_representatives"); - tree_a.add_child ("preconfigured_representatives", reps); - result = true; + account.decode_account (entry); + reps.push (account.to_account ()); + }); + + json.replace_child ("preconfigured_representatives", reps); + upgraded = true; } case 2: { - tree_a.put ("inactive_supply", nano::uint128_union (0).to_string_dec ()); - tree_a.put ("password_fanout", std::to_string (1024)); - tree_a.put ("io_threads", std::to_string (io_threads)); - tree_a.put ("work_threads", std::to_string (work_threads)); - result = true; + json.put ("inactive_supply", nano::uint128_union (0).to_string_dec ()); + json.put ("password_fanout", std::to_string (1024)); + json.put ("io_threads", std::to_string (io_threads)); + json.put ("work_threads", std::to_string (work_threads)); + upgraded = true; } case 3: - tree_a.erase ("receive_minimum"); - tree_a.put ("receive_minimum", nano::xrb_ratio.convert_to ()); - result = true; + json.erase ("receive_minimum"); + json.put ("receive_minimum", nano::xrb_ratio.convert_to ()); + upgraded = true; case 4: - tree_a.erase ("receive_minimum"); - tree_a.put ("receive_minimum", nano::xrb_ratio.convert_to ()); - result = true; + json.erase ("receive_minimum"); + json.put ("receive_minimum", nano::xrb_ratio.convert_to ()); + upgraded = true; case 5: - tree_a.put ("enable_voting", enable_voting); - tree_a.erase ("packet_delay_microseconds"); - tree_a.erase ("rebroadcast_delay"); - tree_a.erase ("creation_rebroadcast"); - result = true; + json.put ("enable_voting", enable_voting); + json.erase ("packet_delay_microseconds"); + json.erase ("rebroadcast_delay"); + json.erase ("creation_rebroadcast"); + upgraded = true; case 6: - tree_a.put ("bootstrap_connections", 16); - tree_a.put ("callback_address", ""); - tree_a.put ("callback_port", "0"); - tree_a.put ("callback_target", ""); - result = true; + json.put ("bootstrap_connections", 16); + json.put ("callback_address", ""); + json.put ("callback_port", 0); + json.put ("callback_target", ""); + upgraded = true; case 7: - tree_a.put ("lmdb_max_dbs", "128"); - result = true; + json.put ("lmdb_max_dbs", 128); + upgraded = true; case 8: - tree_a.put ("bootstrap_connections_max", "64"); - result = true; + json.put ("bootstrap_connections_max", "64"); + upgraded = true; case 9: - tree_a.put ("state_block_parse_canary", nano::block_hash (0).to_string ()); - tree_a.put ("state_block_generate_canary", nano::block_hash (0).to_string ()); - result = true; + json.put ("state_block_parse_canary", nano::block_hash (0).to_string ()); + json.put ("state_block_generate_canary", nano::block_hash (0).to_string ()); + upgraded = true; case 10: - tree_a.put ("online_weight_minimum", online_weight_minimum.to_string_dec ()); - tree_a.put ("online_weight_quorom", std::to_string (online_weight_quorum)); - tree_a.erase ("inactive_supply"); - result = true; + json.put ("online_weight_minimum", online_weight_minimum.to_string_dec ()); + json.put ("online_weight_quorom", std::to_string (online_weight_quorum)); + json.erase ("inactive_supply"); + upgraded = true; case 11: { - auto online_weight_quorum_l (tree_a.get ("online_weight_quorom")); - tree_a.erase ("online_weight_quorom"); - tree_a.put ("online_weight_quorum", online_weight_quorum_l); - result = true; + // Rename + std::string online_weight_quorum_l; + json.get ("online_weight_quorom", online_weight_quorum_l); + json.erase ("online_weight_quorom"); + json.put ("online_weight_quorum", online_weight_quorum_l); + upgraded = true; } case 12: - tree_a.erase ("state_block_parse_canary"); - tree_a.erase ("state_block_generate_canary"); - result = true; + json.erase ("state_block_parse_canary"); + json.erase ("state_block_generate_canary"); + upgraded = true; case 13: - tree_a.put ("generate_hash_votes_at", "0"); - result = true; + json.put ("generate_hash_votes_at", 0); + upgraded = true; case 14: - tree_a.put ("network_threads", std::to_string (network_threads)); - tree_a.erase ("generate_hash_votes_at"); - tree_a.put ("block_processor_batch_max_time", block_processor_batch_max_time.count ()); - result = true; + json.put ("network_threads", std::to_string (network_threads)); + json.erase ("generate_hash_votes_at"); + json.put ("block_processor_batch_max_time", block_processor_batch_max_time.count ()); + upgraded = true; case 15: - tree_a.put ("allow_local_peers", allow_local_peers); - result = true; + json.put ("allow_local_peers", allow_local_peers); + upgraded = true; case 16: break; default: throw std::runtime_error ("Unknown node_config version"); } - return result; + return upgraded; } -bool nano::node_config::deserialize_json (bool & upgraded_a, boost::property_tree::ptree & tree_a) +nano::error nano::node_config::deserialize_json (bool & upgraded_a, nano::jsonconfig & json) { - auto result (false); try { - auto version_l (tree_a.get_optional ("version")); + auto version_l (json.get_optional ("version")); if (!version_l) { - tree_a.put ("version", "1"); - version_l = "1"; - auto work_peers_l (tree_a.get_child_optional ("work_peers")); + version_l = 1; + json.put ("version", version_l); + auto work_peers_l (json.get_optional_child ("work_peers")); if (!work_peers_l) { - tree_a.add_child ("work_peers", boost::property_tree::ptree ()); + nano::jsonconfig empty; + json.put_child ("work_peers", empty); } upgraded_a = true; } - upgraded_a |= upgrade_json (std::stoull (version_l.get ()), tree_a); - auto peering_port_l (tree_a.get ("peering_port")); - auto bootstrap_fraction_numerator_l (tree_a.get ("bootstrap_fraction_numerator")); - auto receive_minimum_l (tree_a.get ("receive_minimum")); - auto & logging_l (tree_a.get_child ("logging")); + + upgraded_a |= upgrade_json (version_l.get (), json); + + auto logging_l (json.get_required_child ("logging")); + logging.deserialize_json (upgraded_a, logging_l); + work_peers.clear (); - auto work_peers_l (tree_a.get_child ("work_peers")); - for (auto i (work_peers_l.begin ()), n (work_peers_l.end ()); i != n; ++i) - { - auto work_peer (i->second.get ("")); - auto port_position (work_peer.rfind (':')); - result |= port_position == -1; + auto work_peers_l (json.get_required_child ("work_peers")); + work_peers_l.array_entries ([this](std::string entry) { + auto port_position (entry.rfind (':')); + bool result = port_position == -1; if (!result) { - auto port_str (work_peer.substr (port_position + 1)); + auto port_str (entry.substr (port_position + 1)); uint16_t port; result |= parse_port (port_str, port); if (!result) { - auto address (work_peer.substr (0, port_position)); - work_peers.push_back (std::make_pair (address, port)); + auto address (entry.substr (0, port_position)); + this->work_peers.push_back (std::make_pair (address, port)); } } - } - auto preconfigured_peers_l (tree_a.get_child ("preconfigured_peers")); + }); + + nano::jsonconfig preconfigured_peers_l; + json.get_required_child ("preconfigured_peers", preconfigured_peers_l); preconfigured_peers.clear (); - for (auto i (preconfigured_peers_l.begin ()), n (preconfigured_peers_l.end ()); i != n; ++i) - { - auto bootstrap_peer (i->second.get ("")); - preconfigured_peers.push_back (bootstrap_peer); - } - auto preconfigured_representatives_l (tree_a.get_child ("preconfigured_representatives")); + preconfigured_peers_l.array_entries ([this](std::string entry) { + preconfigured_peers.push_back (entry); + }); + + nano::jsonconfig preconfigured_representatives_l; + json.get_required_child ("preconfigured_representatives", preconfigured_representatives_l); preconfigured_representatives.clear (); - for (auto i (preconfigured_representatives_l.begin ()), n (preconfigured_representatives_l.end ()); i != n; ++i) - { + preconfigured_representatives_l.array_entries ([this, &json](std::string entry) { nano::account representative (0); - result = result || representative.decode_account (i->second.get ("")); + if (representative.decode_account (entry)) + { + json.get_error ().set ("Invalid representative account: " + entry); + } preconfigured_representatives.push_back (representative); - } + }); + if (preconfigured_representatives.empty ()) { - result = true; + json.get_error ().set ("At least one representative account must be set"); } - auto stat_config_l (tree_a.get_child_optional ("statistics")); + auto stat_config_l (json.get_optional_child ("statistics")); if (stat_config_l) { - result |= stat_config.deserialize_json (stat_config_l.get ()); + stat_config.deserialize_json (stat_config_l.get ()); } - auto online_weight_minimum_l (tree_a.get ("online_weight_minimum")); - auto online_weight_quorum_l (tree_a.get ("online_weight_quorum")); - auto password_fanout_l (tree_a.get ("password_fanout")); - auto io_threads_l (tree_a.get ("io_threads")); - auto work_threads_l (tree_a.get ("work_threads")); - enable_voting = tree_a.get ("enable_voting"); - auto bootstrap_connections_l (tree_a.get ("bootstrap_connections")); - auto bootstrap_connections_max_l (tree_a.get ("bootstrap_connections_max")); - callback_address = tree_a.get ("callback_address"); - auto callback_port_l (tree_a.get ("callback_port")); - callback_target = tree_a.get ("callback_target"); - auto lmdb_max_dbs_l = tree_a.get ("lmdb_max_dbs"); - result |= parse_port (callback_port_l, callback_port); - auto block_processor_batch_max_time_l = tree_a.get ("block_processor_batch_max_time"); - try + + auto receive_minimum_l (json.get ("receive_minimum")); + if (receive_minimum.decode_dec (receive_minimum_l)) { - peering_port = std::stoul (peering_port_l); - bootstrap_fraction_numerator = std::stoul (bootstrap_fraction_numerator_l); - password_fanout = std::stoul (password_fanout_l); - io_threads = std::stoul (io_threads_l); - network_threads = tree_a.get ("network_threads", network_threads); - work_threads = std::stoul (work_threads_l); - bootstrap_connections = std::stoul (bootstrap_connections_l); - bootstrap_connections_max = std::stoul (bootstrap_connections_max_l); - lmdb_max_dbs = std::stoi (lmdb_max_dbs_l); - online_weight_quorum = std::stoul (online_weight_quorum_l); - block_processor_batch_max_time = std::chrono::milliseconds (std::stoul (block_processor_batch_max_time_l)); - result |= peering_port > std::numeric_limits::max (); - result |= logging.deserialize_json (upgraded_a, logging_l); - result |= receive_minimum.decode_dec (receive_minimum_l); - result |= online_weight_minimum.decode_dec (online_weight_minimum_l); - result |= online_weight_quorum > 100; - result |= password_fanout < 16; - result |= password_fanout > 1024 * 1024; - result |= io_threads == 0; + json.get_error ().set ("receive_minimum contains an invalid decimal amount"); } - catch (std::logic_error const &) + + auto online_weight_minimum_l (json.get ("online_weight_minimum")); + if (online_weight_minimum.decode_dec (online_weight_minimum_l)) { - result = true; + json.get_error ().set ("online_weight_minimum contains an invalid decimal amount"); + } + + auto block_processor_batch_max_time_l (json.get ("block_processor_batch_max_time")); + block_processor_batch_max_time = std::chrono::milliseconds (block_processor_batch_max_time_l); + + json.get ("peering_port", peering_port); + json.get ("bootstrap_fraction_numerator", bootstrap_fraction_numerator); + json.get ("online_weight_quorum", online_weight_quorum); + json.get ("password_fanout", password_fanout); + json.get ("io_threads", io_threads); + json.get ("work_threads", work_threads); + json.get ("network_threads", network_threads); + json.get ("bootstrap_connections", bootstrap_connections); + json.get ("bootstrap_connections_max", bootstrap_connections_max); + json.get ("callback_address", callback_address); + json.get ("callback_port", callback_port); + json.get ("callback_target", callback_target); + json.get ("lmdb_max_dbs", lmdb_max_dbs); + json.get ("enable_voting", enable_voting); + + // Validate ranges + + if (online_weight_quorum > 100) + { + json.get_error ().set ("online_weight_quorum must be less than 100"); + } + if (password_fanout < 16 || password_fanout > 1024 * 1024) + { + json.get_error ().set ("password_fanout must a number between 16 and 1048576"); + } + if (io_threads == 0) + { + json.get_error ().set ("io_threads must be non-zero"); } } - catch (std::runtime_error const &) + catch (std::runtime_error const & ex) { - result = true; + json.get_error ().set (ex.what ()); } - return result; + return json.get_error (); } nano::account nano::node_config::random_representative () diff --git a/nano/node/nodeconfig.hpp b/nano/node/nodeconfig.hpp index ecb90d0da..fa57b0d05 100644 --- a/nano/node/nodeconfig.hpp +++ b/nano/node/nodeconfig.hpp @@ -1,7 +1,8 @@ #pragma once -#include #include +#include +#include #include #include #include @@ -17,9 +18,9 @@ class node_config public: node_config (); node_config (uint16_t, nano::logging const &); - void serialize_json (boost::property_tree::ptree &) const; - bool deserialize_json (bool &, boost::property_tree::ptree &); - bool upgrade_json (unsigned, boost::property_tree::ptree &); + nano::error serialize_json (nano::jsonconfig &) const; + nano::error deserialize_json (bool &, nano::jsonconfig &); + bool upgrade_json (unsigned, nano::jsonconfig &); nano::account random_representative (); uint16_t peering_port; nano::logging logging; @@ -49,7 +50,10 @@ public: static std::chrono::seconds constexpr keepalive_period = std::chrono::seconds (60); static std::chrono::seconds constexpr keepalive_cutoff = keepalive_period * 5; static std::chrono::minutes constexpr wallet_backup_interval = std::chrono::minutes (5); - static constexpr int json_version = 16; + inline int json_version () const + { + return 16; + } }; class node_flags diff --git a/nano/node/openclwork.cpp b/nano/node/openclwork.cpp index f617203bc..40514d5d8 100644 --- a/nano/node/openclwork.cpp +++ b/nano/node/openclwork.cpp @@ -512,37 +512,20 @@ threads (threads_a) { } -void nano::opencl_config::serialize_json (boost::property_tree::ptree & tree_a) const +nano::error nano::opencl_config::serialize_json (nano::jsonconfig & json) const { - tree_a.put ("platform", std::to_string (platform)); - tree_a.put ("device", std::to_string (device)); - tree_a.put ("threads", std::to_string (threads)); + json.put ("platform", platform); + json.put ("device", device); + json.put ("threads", threads); + return json.get_error (); } -bool nano::opencl_config::deserialize_json (boost::property_tree::ptree const & tree_a) +nano::error nano::opencl_config::deserialize_json (nano::jsonconfig & json) { - auto result (false); - try - { - auto platform_l (tree_a.get ("platform")); - auto device_l (tree_a.get ("device")); - auto threads_l (tree_a.get ("threads")); - try - { - platform = std::stoull (platform_l); - device = std::stoull (device_l); - threads = std::stoull (threads_l); - } - catch (std::logic_error const &) - { - result = true; - } - } - catch (std::runtime_error const &) - { - result = true; - } - return result; + json.get_optional ("platform", platform); + json.get_optional ("device", device); + json.get_optional ("threads", threads); + return json.get_error (); } nano::opencl_work::opencl_work (bool & error_a, nano::opencl_config const & config_a, nano::opencl_environment & environment_a, nano::logging & logging_a) : diff --git a/nano/node/openclwork.hpp b/nano/node/openclwork.hpp index e6c37b947..004bb6a8a 100644 --- a/nano/node/openclwork.hpp +++ b/nano/node/openclwork.hpp @@ -1,9 +1,10 @@ #pragma once -#include - #include #include +#include +#include +#include #include #include @@ -39,8 +40,8 @@ class opencl_config public: opencl_config (); opencl_config (unsigned, unsigned, unsigned); - void serialize_json (boost::property_tree::ptree &) const; - bool deserialize_json (boost::property_tree::ptree const &); + nano::error serialize_json (nano::jsonconfig &) const; + nano::error deserialize_json (nano::jsonconfig &); unsigned platform; unsigned device; unsigned threads; diff --git a/nano/node/rpc.cpp b/nano/node/rpc.cpp index a4cb6fe36..b4b9aaa56 100644 --- a/nano/node/rpc.cpp +++ b/nano/node/rpc.cpp @@ -1,8 +1,7 @@ #include -#include - #include #include +#include #ifdef NANO_SECURE_RPC #include @@ -16,35 +15,28 @@ verbose_logging (false) { } -void nano::rpc_secure_config::serialize_json (boost::property_tree::ptree & tree_a) const +nano::error nano::rpc_secure_config::serialize_json (nano::jsonconfig & json) const { - tree_a.put ("enable", enable); - tree_a.put ("verbose_logging", verbose_logging); - tree_a.put ("server_key_passphrase", server_key_passphrase); - tree_a.put ("server_cert_path", server_cert_path); - tree_a.put ("server_key_path", server_key_path); - tree_a.put ("server_dh_path", server_dh_path); - tree_a.put ("client_certs_path", client_certs_path); + json.put ("enable", enable); + json.put ("verbose_logging", verbose_logging); + json.put ("server_key_passphrase", server_key_passphrase); + json.put ("server_cert_path", server_cert_path); + json.put ("server_key_path", server_key_path); + json.put ("server_dh_path", server_dh_path); + json.put ("client_certs_path", client_certs_path); + return json.get_error (); } -bool nano::rpc_secure_config::deserialize_json (boost::property_tree::ptree const & tree_a) +nano::error nano::rpc_secure_config::deserialize_json (nano::jsonconfig & json) { - auto error (false); - try - { - enable = tree_a.get ("enable"); - verbose_logging = tree_a.get ("verbose_logging"); - server_key_passphrase = tree_a.get ("server_key_passphrase"); - server_cert_path = tree_a.get ("server_cert_path"); - server_key_path = tree_a.get ("server_key_path"); - server_dh_path = tree_a.get ("server_dh_path"); - client_certs_path = tree_a.get ("client_certs_path"); - } - catch (std::runtime_error const &) - { - error = true; - } - return error; + json.get_required ("enable", enable); + json.get_required ("verbose_logging", verbose_logging); + json.get_required ("server_key_passphrase", server_key_passphrase); + json.get_required ("server_cert_path", server_cert_path); + json.get_required ("server_key_path", server_key_path); + json.get_required ("server_dh_path", server_dh_path); + json.get_required ("client_certs_path", client_certs_path); + return json.get_error (); } nano::rpc_config::rpc_config () : @@ -67,59 +59,33 @@ max_json_depth (20) { } -void nano::rpc_config::serialize_json (boost::property_tree::ptree & tree_a) const +nano::error nano::rpc_config::serialize_json (nano::jsonconfig & json) const { - tree_a.put ("address", address.to_string ()); - tree_a.put ("port", std::to_string (port)); - tree_a.put ("enable_control", enable_control); - tree_a.put ("frontier_request_limit", frontier_request_limit); - tree_a.put ("chain_request_limit", chain_request_limit); - tree_a.put ("max_json_depth", max_json_depth); + json.put ("address", address.to_string ()); + json.put ("port", port); + json.put ("enable_control", enable_control); + json.put ("frontier_request_limit", frontier_request_limit); + json.put ("chain_request_limit", chain_request_limit); + json.put ("max_json_depth", max_json_depth); + return json.get_error (); } -bool nano::rpc_config::deserialize_json (boost::property_tree::ptree const & tree_a) +nano::error nano::rpc_config::deserialize_json (nano::jsonconfig & json) { - auto result (false); - try + boost::optional rpc_secure_l; + json.get_optional_child ("secure", rpc_secure_l); + if (rpc_secure_l) { - auto rpc_secure_l (tree_a.get_child_optional ("secure")); - if (rpc_secure_l) - { - result = secure.deserialize_json (rpc_secure_l.get ()); - } + secure.deserialize_json (*rpc_secure_l); + } - if (!result) - { - auto address_l (tree_a.get ("address")); - auto port_l (tree_a.get ("port")); - enable_control = tree_a.get ("enable_control"); - auto frontier_request_limit_l (tree_a.get ("frontier_request_limit")); - auto chain_request_limit_l (tree_a.get ("chain_request_limit")); - max_json_depth = tree_a.get ("max_json_depth", max_json_depth); - try - { - port = std::stoul (port_l); - result = port > std::numeric_limits::max (); - frontier_request_limit = std::stoull (frontier_request_limit_l); - chain_request_limit = std::stoull (chain_request_limit_l); - } - catch (std::logic_error const &) - { - result = true; - } - boost::system::error_code ec; - address = boost::asio::ip::address_v6::from_string (address_l, ec); - if (ec) - { - result = true; - } - } - } - catch (std::runtime_error const &) - { - result = true; - } - return result; + json.get_required ("address", address); + json.get_optional ("port", port); + json.get_optional ("enable_control", enable_control); + json.get_optional ("frontier_request_limit", frontier_request_limit); + json.get_optional ("chain_request_limit", chain_request_limit); + json.get_optional ("max_json_depth", max_json_depth); + return json.get_error (); } nano::rpc::rpc (boost::asio::io_context & io_ctx_a, nano::node & node_a, nano::rpc_config const & config_a) : diff --git a/nano/node/rpc.hpp b/nano/node/rpc.hpp index e1b98d9e6..772e65d32 100644 --- a/nano/node/rpc.hpp +++ b/nano/node/rpc.hpp @@ -5,6 +5,8 @@ #include #include #include +#include +#include #include #include @@ -17,8 +19,8 @@ class rpc_secure_config { public: rpc_secure_config (); - void serialize_json (boost::property_tree::ptree &) const; - bool deserialize_json (boost::property_tree::ptree const &); + nano::error serialize_json (nano::jsonconfig &) const; + nano::error deserialize_json (nano::jsonconfig &); /** If true, enable TLS */ bool enable; @@ -40,8 +42,8 @@ class rpc_config public: rpc_config (); rpc_config (bool); - void serialize_json (boost::property_tree::ptree &) const; - bool deserialize_json (boost::property_tree::ptree const &); + nano::error serialize_json (nano::jsonconfig &) const; + nano::error deserialize_json (nano::jsonconfig &); boost::asio::ip::address_v6 address; uint16_t port; bool enable_control; diff --git a/nano/node/stats.cpp b/nano/node/stats.cpp index 6efd8b2d0..231c51174 100644 --- a/nano/node/stats.cpp +++ b/nano/node/stats.cpp @@ -9,33 +9,34 @@ #include #include -bool nano::stat_config::deserialize_json (boost::property_tree::ptree & tree_a) +nano::error nano::stat_config::deserialize_json (nano::jsonconfig & json) { - bool error = false; - - auto sampling_l (tree_a.get_child_optional ("sampling")); + auto sampling_l (json.get_optional_child ("sampling")); if (sampling_l) { - sampling_enabled = sampling_l->get ("enabled", sampling_enabled); - capacity = sampling_l->get ("capacity", capacity); - interval = sampling_l->get ("interval", interval); + sampling_l->get ("enabled", sampling_enabled); + sampling_l->get ("capacity", capacity); + sampling_l->get ("interval", interval); } - auto log_l (tree_a.get_child_optional ("log")); + auto log_l (json.get_optional_child ("log")); if (log_l) { - log_headers = log_l->get ("headers", log_headers); - log_interval_counters = log_l->get ("interval_counters", log_interval_counters); - log_interval_samples = log_l->get ("interval_samples", log_interval_samples); - log_rotation_count = log_l->get ("rotation_count", log_rotation_count); - log_counters_filename = log_l->get ("filename_counters", log_counters_filename); - log_samples_filename = log_l->get ("filename_samples", log_samples_filename); + log_l->get ("headers", log_headers); + log_l->get ("interval_counters", log_interval_counters); + log_l->get ("interval_samples", log_interval_samples); + log_l->get ("rotation_count", log_rotation_count); + log_l->get ("filename_counters", log_counters_filename); + log_l->get ("filename_samples", log_samples_filename); // Don't allow specifying the same file name for counter and samples logs - error = (log_counters_filename == log_samples_filename); + if (log_counters_filename == log_samples_filename) + { + json.get_error ().set ("The statistics counter and samples config values must be different"); + } } - return error; + return json.get_error (); } std::string nano::stat_log_sink::tm_to_string (tm & tm) diff --git a/nano/node/stats.hpp b/nano/node/stats.hpp index ecd8901fc..30e17b87f 100644 --- a/nano/node/stats.hpp +++ b/nano/node/stats.hpp @@ -6,6 +6,8 @@ #include #include #include +#include +#include #include #include #include @@ -23,7 +25,7 @@ class stat_config { public: /** Reads the JSON statistics node */ - bool deserialize_json (boost::property_tree::ptree & tree_a); + nano::error deserialize_json (nano::jsonconfig & json); /** If true, sampling of counters is enabled */ bool sampling_enabled{ false }; diff --git a/nano/secure/utility.cpp b/nano/secure/utility.cpp index 22c978476..b59741493 100644 --- a/nano/secure/utility.cpp +++ b/nano/secure/utility.cpp @@ -109,14 +109,3 @@ std::vector nano::remove_temporary_directories () } return all_unique_paths; } - -void nano::open_or_create (std::fstream & stream_a, std::string const & path_a) -{ - stream_a.open (path_a, std::ios_base::in); - if (stream_a.fail ()) - { - stream_a.open (path_a, std::ios_base::out); - } - stream_a.close (); - stream_a.open (path_a, std::ios_base::in | std::ios_base::out); -} diff --git a/nano/secure/utility.hpp b/nano/secure/utility.hpp index 9b8dd8b4f..0708958c4 100644 --- a/nano/secure/utility.hpp +++ b/nano/secure/utility.hpp @@ -13,6 +13,7 @@ #include #include +#include #include #include @@ -29,81 +30,4 @@ bool migrate_working_path (std::string &); boost::filesystem::path unique_path (); // Remove all unique tmp directories created by the process. The list of unique paths are returned. std::vector remove_temporary_directories (); -// C++ stream are absolutely horrible so I need this helper function to do the most basic operation of creating a file if it doesn't exist or truncating it. -void open_or_create (std::fstream &, std::string const &); -// Reads a json object from the stream and if was changed, write the object back to the stream -template -bool fetch_object (T & object, std::iostream & stream_a) -{ - assert (stream_a.tellg () == std::streampos (0) || stream_a.tellg () == std::streampos (-1)); - assert (stream_a.tellp () == std::streampos (0) || stream_a.tellp () == std::streampos (-1)); - bool error (false); - boost::property_tree::ptree tree; - try - { - boost::property_tree::read_json (stream_a, tree); - } - catch (std::runtime_error const &) - { - auto pos (stream_a.tellg ()); - if (pos != std::streampos (0)) - { - error = true; - } - } - if (!error) - { - auto updated (false); - error = object.deserialize_json (updated, tree); - } - return error; -} -// Reads a json object from the stream and if was changed, write the object back to the stream -template -bool fetch_object (T & object, boost::filesystem::path const & path_a) -{ - bool error (false); - std::fstream config_file; - nano::open_or_create (config_file, path_a.string ()); - - if (!config_file.fail ()) - { - boost::property_tree::ptree tree; - try - { - boost::property_tree::read_json (config_file, tree); - } - catch (std::runtime_error const &) - { - auto pos (config_file.tellg ()); - if (pos != std::streampos (0)) - { - error = true; - } - } - - config_file.close (); - - if (!error) - { - auto updated (false); - error = object.deserialize_json (updated, tree); - if (!error && updated) - { - config_file.open (path_a.string (), std::ios_base::out | std::ios_base::trunc); - try - { - boost::property_tree::write_json (config_file, tree); - } - catch (std::runtime_error const &) - { - error = true; - } - config_file.close (); - } - } - } - - return error; -} }