diff --git a/libraries/app/api_objects.cpp b/libraries/app/api_objects.cpp index e002f061ae..a999ad99ca 100644 --- a/libraries/app/api_objects.cpp +++ b/libraries/app/api_objects.cpp @@ -38,8 +38,11 @@ market_ticker::market_ticker(const market_ticker_object& mto, quote = asset_quote.symbol; percent_change = "0"; lowest_ask = "0"; + lowest_ask_base_size = "0"; + lowest_ask_quote_size = "0"; highest_bid = "0"; - + highest_bid_base_size = "0"; + highest_bid_quote_size = "0"; fc::uint128_t bv; fc::uint128_t qv; price latest_price = asset( mto.latest_base, mto.base ) / asset( mto.latest_quote, mto.quote ); @@ -68,9 +71,19 @@ market_ticker::market_ticker(const market_ticker_object& mto, quote_volume = uint128_amount_to_string( qv, asset_quote.precision ); if(!orders.asks.empty()) - lowest_ask = orders.asks[0].price; + { + lowest_ask = orders.asks[0].price; + lowest_ask_base_size = orders.asks[0].base; + lowest_ask_quote_size = orders.asks[0].quote; + } + if(!orders.bids.empty()) - highest_bid = orders.bids[0].price; + { + highest_bid = orders.bids[0].price; + highest_bid_base_size = orders.bids[0].base; + highest_bid_quote_size = orders.bids[0].quote; + } + } market_ticker::market_ticker(const fc::time_point_sec& now, @@ -82,7 +95,11 @@ market_ticker::market_ticker(const fc::time_point_sec& now, quote = asset_quote.symbol; latest = "0"; lowest_ask = "0"; + lowest_ask_base_size = "0"; + lowest_ask_quote_size = "0"; highest_bid = "0"; + highest_bid_base_size = "0"; + highest_bid_quote_size = "0"; percent_change = "0"; base_volume = "0"; quote_volume = "0"; diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 16de823ddd..6263ab1ec8 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -43,7 +43,6 @@ #include #include #include -#include #include #include @@ -125,41 +124,14 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) if( _options->count("seed-node") ) { auto seeds = _options->at("seed-node").as>(); - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - _p2p_network->connect_to_endpoint(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } if( _options->count("seed-nodes") ) { auto seeds_str = _options->at("seed-nodes").as(); auto seeds = fc::json::from_string(seeds_str).as>(2); - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } else { @@ -167,20 +139,7 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) vector seeds = { #include "../egenesis/seed-nodes.txt" }; - for( const string& endpoint_string : seeds ) - { - try { - std::vector endpoints = resolve_string_to_ip_endpoints(endpoint_string); - for (const fc::ip::endpoint& endpoint : endpoints) - { - ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); - _p2p_network->add_node(endpoint); - } - } catch( const fc::exception& e ) { - wlog( "caught exception ${e} while adding seed node ${endpoint}", - ("e", e.to_detail_string())("endpoint", endpoint_string) ); - } - } + _p2p_network->add_seed_nodes(seeds); } if( _options->count("p2p-endpoint") ) @@ -196,36 +155,6 @@ void application_impl::reset_p2p_node(const fc::path& data_dir) std::vector()); } FC_CAPTURE_AND_RETHROW() } -std::vector application_impl::resolve_string_to_ip_endpoints(const std::string& endpoint_string) -{ - try - { - string::size_type colon_pos = endpoint_string.find(':'); - if (colon_pos == std::string::npos) - FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", - ("endpoint_string", endpoint_string)); - std::string port_string = endpoint_string.substr(colon_pos + 1); - try - { - uint16_t port = boost::lexical_cast(port_string); - - std::string hostname = endpoint_string.substr(0, colon_pos); - std::vector endpoints = fc::resolve(hostname, port); - if (endpoints.empty()) - FC_THROW_EXCEPTION( fc::unknown_host_exception, - "The host name can not be resolved: ${hostname}", - ("hostname", hostname) ); - return endpoints; - } - catch (const boost::bad_lexical_cast&) - { - FC_THROW("Bad port: ${port}", ("port", port_string)); - } - } - FC_CAPTURE_AND_RETHROW((endpoint_string)) -} - - void application_impl::new_connection( const fc::http::websocket_connection_ptr& c ) { auto wsc = std::make_shared(c, GRAPHENE_NET_MAX_NESTED_OBJECTS); diff --git a/libraries/app/application_impl.hxx b/libraries/app/application_impl.hxx index 175648e10f..accc8fe4f1 100644 --- a/libraries/app/application_impl.hxx +++ b/libraries/app/application_impl.hxx @@ -22,8 +22,6 @@ class application_impl : public net::node_delegate void reset_p2p_node(const fc::path& data_dir); - std::vector resolve_string_to_ip_endpoints(const std::string& endpoint_string); - void new_connection( const fc::http::websocket_connection_ptr& c ); void reset_websocket_server(); diff --git a/libraries/app/include/graphene/app/api_objects.hpp b/libraries/app/include/graphene/app/api_objects.hpp index af7a96e1ab..ff7f027f09 100644 --- a/libraries/app/include/graphene/app/api_objects.hpp +++ b/libraries/app/include/graphene/app/api_objects.hpp @@ -100,7 +100,11 @@ namespace graphene { namespace app { string quote; string latest; string lowest_ask; + string lowest_ask_base_size; + string lowest_ask_quote_size; string highest_bid; + string highest_bid_base_size; + string highest_bid_quote_size; string percent_change; string base_volume; string quote_volume; @@ -178,7 +182,8 @@ FC_REFLECT( graphene::app::full_account, FC_REFLECT( graphene::app::order, (price)(quote)(base) ); FC_REFLECT( graphene::app::order_book, (base)(quote)(bids)(asks) ); FC_REFLECT( graphene::app::market_ticker, - (time)(base)(quote)(latest)(lowest_ask)(highest_bid)(percent_change)(base_volume)(quote_volume) ); + (time)(base)(quote)(latest)(lowest_ask)(lowest_ask_base_size)(lowest_ask_quote_size) + (highest_bid)(highest_bid_base_size)(highest_bid_quote_size)(percent_change)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_volume, (time)(base)(quote)(base_volume)(quote_volume) ); FC_REFLECT( graphene::app::market_trade, (sequence)(date)(price)(amount)(value)(side1_account_id)(side2_account_id) ); diff --git a/libraries/chain/asset_evaluator.cpp b/libraries/chain/asset_evaluator.cpp index 0427ff71a3..1f30290c87 100644 --- a/libraries/chain/asset_evaluator.cpp +++ b/libraries/chain/asset_evaluator.cpp @@ -50,7 +50,7 @@ namespace detail { void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options) { if (block_time < HARDFORK_BSIP_81_TIME) { - // Taker fees should be zero until activation of BSIP81 + // Taker fees should not be set until activation of BSIP81 FC_ASSERT(!options.extensions.value.taker_fee_percent.valid(), "Taker fee percent should not be defined before HARDFORK_BSIP_81_TIME"); } @@ -65,7 +65,16 @@ namespace detail { "Initial collateral ratio should not be defined before HARDFORK_BSIP_77_TIME"); } } -} + + void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op) + { + // HF_REMOVABLE: Following hardfork check should be removable after hardfork date passes: + FC_ASSERT( !op.extensions.value.claim_from_asset_id.valid() || + block_time >= HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME, + "Collateral-denominated fees are not yet active and therefore cannot be claimed." ); + } + +} // graphene::chain::detail void_result asset_create_evaluator::do_evaluate( const asset_create_operation& op ) { try { @@ -468,6 +477,9 @@ void_result asset_update_bitasset_evaluator::do_evaluate(const asset_update_bita FC_ASSERT( asset_obj.dynamic_asset_data_id(d).current_supply == 0, "Cannot update a bitasset if there is already a current supply." ); + FC_ASSERT( asset_obj.dynamic_asset_data_id(d).accumulated_collateral_fees == 0, + "Must claim collateral-denominated fees before changing backing asset." ); + const asset_object& new_backing_asset = op.new_options.short_backing_asset(d); // check if the asset exists if( after_hf_core_922_931 ) @@ -944,25 +956,58 @@ void_result asset_publish_feeds_evaluator::do_apply(const asset_publish_feed_ope } FC_CAPTURE_AND_RETHROW((o)) } - +/*** + * @brief evaluator for asset_claim_fees operation + * + * Checks that we are able to claim fees denominated in asset Y (the amount_to_claim asset), + * from some container asset X which is presumed to have accumulated the fees we wish to claim. + * The container asset is either explicitly named in the extensions, or else assumed as the same + * asset as the amount_to_claim asset. Evaluation fails if either (a) operation issuer is not + * the same as the container_asset issuer, or (b) container_asset has no fee bucket for + * amount_to_claim asset, or (c) accumulated fees are insufficient to cover amount claimed. + */ void_result asset_claim_fees_evaluator::do_evaluate( const asset_claim_fees_operation& o ) { try { - FC_ASSERT( o.amount_to_claim.asset_id(db()).issuer == o.issuer, "Asset fees may only be claimed by the issuer" ); + const database& d = db(); + + detail::check_asset_claim_fees_hardfork_87_74_collatfee(d.head_block_time(), o); // HF_REMOVABLE + + container_asset = o.extensions.value.claim_from_asset_id.valid() ? + &(*o.extensions.value.claim_from_asset_id)(d) : &o.amount_to_claim.asset_id(d); + + FC_ASSERT( container_asset->issuer == o.issuer, "Asset fees may only be claimed by the issuer" ); + FC_ASSERT( container_asset->can_accumulate_fee(d,o.amount_to_claim), + "Asset ${a} (${id}) is not backed by asset (${fid}) and does not hold it as fees.", + ("a",container_asset->symbol)("id",container_asset->id)("fid",o.amount_to_claim.asset_id) ); + + container_ddo = &container_asset->dynamic_asset_data_id(d); + + FC_ASSERT( o.amount_to_claim.amount <= ((container_asset->get_id() == o.amount_to_claim.asset_id) ? + container_ddo->accumulated_fees : + container_ddo->accumulated_collateral_fees), + "Attempt to claim more fees than have accumulated within asset ${a} (${id})", + ("a",container_asset->symbol)("id",container_asset->id)("ddo",*container_ddo) ); + return void_result(); } FC_CAPTURE_AND_RETHROW( (o) ) } +/*** + * @brief apply asset_claim_fees operation + */ void_result asset_claim_fees_evaluator::do_apply( const asset_claim_fees_operation& o ) { try { database& d = db(); - const asset_object& a = o.amount_to_claim.asset_id(d); - const asset_dynamic_data_object& addo = a.dynamic_asset_data_id(d); - FC_ASSERT( o.amount_to_claim.amount <= addo.accumulated_fees, "Attempt to claim more fees than have accumulated", ("addo",addo) ); - - d.modify( addo, [&]( asset_dynamic_data_object& _addo ) { - _addo.accumulated_fees -= o.amount_to_claim.amount; - }); + if ( container_asset->get_id() == o.amount_to_claim.asset_id ) { + d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo ) { + _addo.accumulated_fees -= o.amount_to_claim.amount; + }); + } else { + d.modify( *container_ddo, [&o]( asset_dynamic_data_object& _addo ) { + _addo.accumulated_collateral_fees -= o.amount_to_claim.amount; + }); + } d.adjust_balance( o.issuer, o.amount_to_claim ); diff --git a/libraries/chain/asset_object.cpp b/libraries/chain/asset_object.cpp index 7da399444d..e442465d36 100644 --- a/libraries/chain/asset_object.cpp +++ b/libraries/chain/asset_object.cpp @@ -205,7 +205,7 @@ string asset_object::amount_to_string(share_type amount) const } FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_dynamic_data_object, (graphene::db::object), - (current_supply)(confidential_supply)(accumulated_fees)(fee_pool) ) + (current_supply)(confidential_supply)(accumulated_fees)(accumulated_collateral_fees)(fee_pool) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::asset_bitasset_data_object, (graphene::db::object), (asset_id) diff --git a/libraries/chain/hardfork.d/CORE_BSIP64.hf b/libraries/chain/hardfork.d/CORE_BSIP64.hf new file mode 100644 index 0000000000..a035996482 --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_BSIP64.hf @@ -0,0 +1,4 @@ +// bitshares BSIP 64 HTLC modifications +#ifndef HARDFORK_CORE_BSIP64_TIME +#define HARDFORK_CORE_BSIP64_TIME (fc::time_point_sec( 1600000000 ) ) // Sep 2020 +#endif diff --git a/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf b/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf new file mode 100644 index 0000000000..3f1add317a --- /dev/null +++ b/libraries/chain/hardfork.d/CORE_BSIP_87_74_COLLATFEE.hf @@ -0,0 +1,7 @@ +// This hardfork enables the extension to asset_claim_fees_operation to claim collateral-denominated fees. +// These fees are collected by BSIPs 87 and 74. This should be set to match the earlier of either +// HARDFORK_CORE_BSIP87_TIME or HARDFORK_CORE_BSIP74_TIME. +// This hardfork check should be removable after the hardfork date passes. +#ifndef HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME +#define HARDFORK_CORE_BSIP_87_74_COLLATFEE_TIME (fc::time_point_sec( 1679955066 ) ) // Temporary date until actual hardfork date is set +#endif diff --git a/libraries/chain/htlc_evaluator.cpp b/libraries/chain/htlc_evaluator.cpp index 008980b9b3..339c2fc4bb 100644 --- a/libraries/chain/htlc_evaluator.cpp +++ b/libraries/chain/htlc_evaluator.cpp @@ -29,6 +29,42 @@ namespace graphene { namespace chain { + namespace detail + { + void check_htlc_create_hf_bsip64(const fc::time_point_sec& block_time, + const htlc_create_operation& op, const asset_object& asset_to_transfer) + { + if (block_time < HARDFORK_CORE_BSIP64_TIME) + { + // memo field added at harfork BSIP64 + // NOTE: both of these checks can be removed after hardfork time + FC_ASSERT( !op.extensions.value.memo.valid(), + "Memo unavailable until after HARDFORK BSIP64"); + // HASH160 added at hardfork BSIP64 + FC_ASSERT( !op.preimage_hash.is_type(), + "HASH160 unavailable until after HARDFORK BSIP64" ); + } + else + { + // this can be moved to the normal non-hf checks after HF_BSIP64 + // IF there were no restricted transfers before HF_BSIP64 + FC_ASSERT( !asset_to_transfer.is_transfer_restricted() + || op.from == asset_to_transfer.issuer || op.to == asset_to_transfer.issuer, + "Asset ${asset} cannot be transfered.", ("asset", asset_to_transfer.id) ); + } + } + + void check_htlc_redeem_hf_bsip64(const fc::time_point_sec& block_time, + const htlc_redeem_operation& op, const htlc_object* htlc_obj) + { + // TODO: The hardfork portion of this check can be removed if no HTLC redemptions are + // attempted on an HTLC with a 0 preimage size before the hardfork date. + if ( htlc_obj->conditions.hash_lock.preimage_size > 0U || + block_time < HARDFORK_CORE_BSIP64_TIME ) + FC_ASSERT(op.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size, + "Preimage size mismatch."); + } + } // end of graphene::chain::details optional get_committee_htlc_options(graphene::chain::database& db) { @@ -43,14 +79,17 @@ namespace graphene { FC_ASSERT(htlc_options, "HTLC Committee options are not set."); // make sure the expiration is reasonable - FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, "HTLC Timeout exceeds allowed length" ); + FC_ASSERT( o.claim_period_seconds <= htlc_options->max_timeout_secs, + "HTLC Timeout exceeds allowed length" ); // make sure the preimage length is reasonable - FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, "HTLC preimage length exceeds allowed length" ); + FC_ASSERT( o.preimage_size <= htlc_options->max_preimage_size, + "HTLC preimage length exceeds allowed length" ); // make sure the sender has the funds for the HTLC FC_ASSERT( d.get_balance( o.from, o.amount.asset_id) >= (o.amount), "Insufficient funds") ; const auto& asset_to_transfer = o.amount.asset_id( d ); const auto& from_account = o.from( d ); const auto& to_account = o.to( d ); + detail::check_htlc_create_hf_bsip64(d.head_block_time(), o, asset_to_transfer); FC_ASSERT( is_authorized_asset( d, from_account, asset_to_transfer ), "Asset ${asset} is not authorized for account ${acct}.", ( "asset", asset_to_transfer.id )( "acct", from_account.id ) ); @@ -73,6 +112,8 @@ namespace graphene { esc.transfer.asset_id = o.amount.asset_id; esc.conditions.hash_lock.preimage_hash = o.preimage_hash; esc.conditions.hash_lock.preimage_size = o.preimage_size; + if ( o.extensions.value.memo.valid() ) + esc.memo = o.extensions.value.memo; esc.conditions.time_lock.expiration = dbase.head_block_time() + o.claim_period_seconds; }); return esc.id; @@ -99,9 +140,10 @@ namespace graphene { void_result htlc_redeem_evaluator::do_evaluate(const htlc_redeem_operation& o) { - htlc_obj = &db().get(o.htlc_id); + auto& d = db(); + htlc_obj = &d.get(o.htlc_id); + detail::check_htlc_redeem_hf_bsip64(d.head_block_time(), o, htlc_obj); - FC_ASSERT(o.preimage.size() == htlc_obj->conditions.hash_lock.preimage_size, "Preimage size mismatch."); const htlc_redeem_visitor vtor( o.preimage ); FC_ASSERT( htlc_obj->conditions.hash_lock.preimage_hash.visit( vtor ), "Provided preimage does not generate correct hash."); @@ -115,7 +157,8 @@ namespace graphene { db().adjust_balance(htlc_obj->transfer.to, amount); // notify related parties htlc_redeemed_operation virt_op( htlc_obj->id, htlc_obj->transfer.from, htlc_obj->transfer.to, o.redeemer, - amount, htlc_obj->conditions.hash_lock.preimage_hash, htlc_obj->conditions.hash_lock.preimage_size ); + amount, htlc_obj->conditions.hash_lock.preimage_hash, htlc_obj->conditions.hash_lock.preimage_size, + o.preimage ); db().push_applied_operation( virt_op ); db().remove(*htlc_obj); return void_result(); diff --git a/libraries/chain/include/graphene/chain/asset_evaluator.hpp b/libraries/chain/include/graphene/chain/asset_evaluator.hpp index 544b4b8b21..068f2cf93e 100644 --- a/libraries/chain/include/graphene/chain/asset_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/asset_evaluator.hpp @@ -166,6 +166,9 @@ namespace graphene { namespace chain { void_result do_evaluate( const asset_claim_fees_operation& o ); void_result do_apply( const asset_claim_fees_operation& o ); + + const asset_object* container_asset = nullptr; + const asset_dynamic_data_object* container_ddo = nullptr; }; class asset_claim_pool_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/asset_object.hpp b/libraries/chain/include/graphene/chain/asset_object.hpp index d28eb1a5cb..97255a9610 100644 --- a/libraries/chain/include/graphene/chain/asset_object.hpp +++ b/libraries/chain/include/graphene/chain/asset_object.hpp @@ -65,6 +65,7 @@ namespace graphene { namespace chain { share_type current_supply; share_type confidential_supply; ///< total asset held in confidential balances share_type accumulated_fees; ///< fees accumulate to be paid out over time + share_type accumulated_collateral_fees; ///< accumulated collateral-denominated fees (for bitassets) share_type fee_pool; ///< in core asset }; @@ -164,6 +165,46 @@ namespace graphene { namespace chain { template share_type reserved( const DB& db )const { return options.max_supply - dynamic_data(db).current_supply; } + + /// @return true if asset can accumulate fees in the given denomination + template + bool can_accumulate_fee(const DB& db, const asset& fee) const { + return (( fee.asset_id == get_id() ) || + ( is_market_issued() && fee.asset_id == bitasset_data(db).options.short_backing_asset )); + } + + /*** + * @brief receive a fee asset to accrue in dynamic_data object + * + * Asset owners define various fees (market fees, force-settle fees, etc.) to be + * collected for the asset owners. These fees are typically denominated in the asset + * itself, but for bitassets some of the fees are denominated in the collateral + * asset. This will place the fee in the right container. + */ + template + void accumulate_fee(DB& db, const asset& fee) const + { + if (fee.amount == 0) return; + FC_ASSERT( fee.amount >= 0, "Fee amount must be non-negative." ); + const auto& dyn_data = dynamic_asset_data_id(db); + if (fee.asset_id == get_id()) { // fee same as asset + db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){ + obj.accumulated_fees += fee.amount; + }); + } else { // fee different asset; perhaps collateral-denominated fee + FC_ASSERT( is_market_issued(), + "Asset ${a} (${id}) cannot accept fee of asset (${fid}).", + ("a",this->symbol)("id",this->id)("fid",fee.asset_id) ); + const auto & bad = bitasset_data(db); + FC_ASSERT( fee.asset_id == bad.options.short_backing_asset, + "Asset ${a} (${id}) cannot accept fee of asset (${fid}).", + ("a",this->symbol)("id",this->id)("fid",fee.asset_id) ); + db.modify( dyn_data, [&fee]( asset_dynamic_data_object& obj ){ + obj.accumulated_collateral_fees += fee.amount; + }); + } + } + }; /** diff --git a/libraries/chain/include/graphene/chain/htlc_object.hpp b/libraries/chain/include/graphene/chain/htlc_object.hpp index 6679c6b636..a9f8cf5f43 100644 --- a/libraries/chain/include/graphene/chain/htlc_object.hpp +++ b/libraries/chain/include/graphene/chain/htlc_object.hpp @@ -59,6 +59,8 @@ namespace graphene { namespace chain { } time_lock; } conditions; + fc::optional memo; + /**** * Index helper for timelock */ diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index 086f7b26a9..544e07ef3e 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -32,6 +32,7 @@ namespace detail { void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options); void check_asset_options_hf_bsip81(const fc::time_point_sec& block_time, const asset_options& options); void check_bitasset_options_hf_bsip77(const fc::time_point_sec& block_time, const bitasset_options& options); + void check_asset_claim_fees_hardfork_87_74_collatfee(const fc::time_point_sec& block_time, const asset_claim_fees_operation& op); } struct proposal_operation_hardfork_visitor @@ -70,9 +71,14 @@ struct proposal_operation_hardfork_visitor detail::check_bitasset_options_hf_bsip77( block_time, v.new_options ); } + void operator()(const graphene::chain::asset_claim_fees_operation &v) const { + detail::check_asset_claim_fees_hardfork_87_74_collatfee(block_time, v); // HF_REMOVABLE + } + void operator()(const graphene::chain::committee_member_update_global_parameters_operation &op) const { if (block_time < HARDFORK_CORE_1468_TIME) { - FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), "Unable to set HTLC options before hardfork 1468"); + FC_ASSERT(!op.new_parameters.extensions.value.updatable_htlc_options.valid(), + "Unable to set HTLC options before hardfork 1468"); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); FC_ASSERT(!op.new_parameters.current_fees->exists()); @@ -98,6 +104,16 @@ struct proposal_operation_hardfork_visitor } void operator()(const graphene::chain::htlc_create_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); + if (block_time < HARDFORK_CORE_BSIP64_TIME) + { + // memo field added at harfork BSIP64 + // NOTE: both of these checks can be removed after hardfork time + FC_ASSERT( !op.extensions.value.memo.valid(), + "Memo unavailable until after HARDFORK BSIP64"); + // HASH160 added at hardfork BSIP64 + FC_ASSERT( !op.preimage_hash.is_type(), + "HASH160 unavailable until after HARDFORK BSIP64" ); + } } void operator()(const graphene::chain::htlc_redeem_operation &op) const { FC_ASSERT( block_time >= HARDFORK_CORE_1468_TIME, "Not allowed until hardfork 1468" ); @@ -125,7 +141,8 @@ struct proposal_operation_hardfork_visitor // Do not allow more than 1 proposal_update in a proposal if ( op.op.is_type() ) { - FC_ASSERT( !already_contains_proposal_update, "At most one proposal update can be nested in a proposal!" ); + FC_ASSERT( !already_contains_proposal_update, + "At most one proposal update can be nested in a proposal!" ); already_contains_proposal_update = true; } } @@ -187,8 +204,9 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat FC_ASSERT( o.expiration_time > block_time, "Proposal has already expired on creation." ); FC_ASSERT( o.expiration_time <= block_time + global_parameters.maximum_proposal_lifetime, "Proposal expiration time is too far in the future." ); - FC_ASSERT( !o.review_period_seconds || fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ), - "Proposal review period must be less than its overall lifetime." ); + FC_ASSERT( !o.review_period_seconds || + fc::seconds( *o.review_period_seconds ) < ( o.expiration_time - block_time ), + "Proposal review period must be less than its overall lifetime." ); // Find all authorities required by the proposed operations flat_set tmp_required_active_auths; @@ -198,8 +216,8 @@ void_result proposal_create_evaluator::do_evaluate( const proposal_create_operat operation_get_required_authorities( op.op, tmp_required_active_auths, _required_owner_auths, other, MUST_IGNORE_CUSTOM_OP_REQD_AUTHS( block_time ) ); } - // All accounts which must provide both owner and active authority should be omitted from the active authority set; - // owner authority approval implies active authority approval. + // All accounts which must provide both owner and active authority should be omitted from the + // active authority set; owner authority approval implies active authority approval. std::set_difference( tmp_required_active_auths.begin(), tmp_required_active_auths.end(), _required_owner_auths.begin(), _required_owner_auths.end(), std::inserter( _required_active_auths, _required_active_auths.begin() ) ); @@ -294,9 +312,9 @@ void_result proposal_update_evaluator::do_apply(const proposal_update_operation& { try { database& d = db(); - // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal skip - // signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet 100% sure the - // required approvals are sufficient to authorize the transaction. + // Potential optimization: if _executed_proposal is true, we can skip the modify step and make push_proposal + // skip signature checks. This isn't done now because I just wrote all the proposals code, and I'm not yet + // 100% sure the required approvals are sufficient to authorize the transaction. d.modify(*_proposal, [&o](proposal_object& p) { p.available_active_approvals.insert(o.active_approvals_to_add.begin(), o.active_approvals_to_add.end()); p.available_owner_approvals.insert(o.owner_approvals_to_add.begin(), o.owner_approvals_to_add.end()); diff --git a/libraries/chain/small_objects.cpp b/libraries/chain/small_objects.cpp index 0233edc54a..c308519e4e 100644 --- a/libraries/chain/small_objects.cpp +++ b/libraries/chain/small_objects.cpp @@ -128,7 +128,7 @@ FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info::ti FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object::condition_info, BOOST_PP_SEQ_NIL, (hash_lock)(time_lock) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::htlc_object, (graphene::db::object), - (transfer) (conditions) ) + (transfer) (conditions) (memo) ) FC_REFLECT_DERIVED_NO_TYPENAME( graphene::chain::operation_history_object, (graphene::chain::object), (op)(result)(block_num)(trx_in_block)(op_in_trx)(virtual_op) ) diff --git a/libraries/fc b/libraries/fc index 23f0c6ab8e..73a7f08f00 160000 --- a/libraries/fc +++ b/libraries/fc @@ -1 +1 @@ -Subproject commit 23f0c6ab8e9516c8d0bc62b64f3ebedfe2f698b6 +Subproject commit 73a7f08f00456b0984cd431dd8f55bd901282e15 diff --git a/libraries/net/include/graphene/net/node.hpp b/libraries/net/include/graphene/net/node.hpp index fe03ac0cb6..43aa94a6e9 100644 --- a/libraries/net/include/graphene/net/node.hpp +++ b/libraries/net/include/graphene/net/node.hpp @@ -211,11 +211,34 @@ namespace graphene { namespace net { */ void add_node( const fc::ip::endpoint& ep ); + /***** + * @brief add a list of nodes to seed the p2p network + * @param seeds a vector of url strings + */ + void add_seed_nodes( std::vector seeds ); + + /**** + * @brief add a node to seed the p2p network + * @param in the url as a string + */ + void add_seed_node( const std::string& in); + /** * Attempt to connect to the specified endpoint immediately. */ virtual void connect_to_endpoint( const fc::ip::endpoint& ep ); + /** + * @brief Helper to convert a string to a collection of endpoints + * + * This converts a string (i.e. "bitshares.eu:665535" to a collection of endpoints. + * NOTE: Throws an exception if not in correct format or was unable to resolve URL. + * + * @param in the incoming string + * @returns a vector of endpoints + */ + static std::vector resolve_string_to_ip_endpoints( const std::string& in ); + /** * Specifies the network interface and port upon which incoming * connections should be accepted. diff --git a/libraries/net/node.cpp b/libraries/net/node.cpp index ec2f6a1f80..5f5227ba98 100644 --- a/libraries/net/node.cpp +++ b/libraries/net/node.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include @@ -70,6 +71,7 @@ #include #include #include +#include #include #include @@ -481,6 +483,43 @@ namespace graphene { namespace net { namespace detail { // _retrigger_connect_loop_promise->set_value(); } + void node_impl::update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + + try + { + dlog("Starting an iteration of update_seed_nodes loop."); + for( const std::string& endpoint_string : _seed_nodes ) + { + resolve_seed_node_and_add( endpoint_string ); + } + dlog("Done an iteration of update_seed_nodes loop."); + } + catch (const fc::canceled_exception&) + { + throw; + } + FC_CAPTURE_AND_LOG( (_seed_nodes) ) + + schedule_next_update_seed_nodes_task(); + } + + void node_impl::schedule_next_update_seed_nodes_task() + { + VERIFY_CORRECT_THREAD(); + + if( _node_is_shutting_down ) + return; + + if( _update_seed_nodes_loop_done.valid() && _update_seed_nodes_loop_done.canceled() ) + return; + + _update_seed_nodes_loop_done = fc::schedule( [this]() { update_seed_nodes_task(); }, + fc::time_point::now() + fc::hours(3), + "update_seed_nodes_loop" ); + } + bool node_impl::have_already_received_sync_item( const item_hash_t& item_hash ) { VERIFY_CORRECT_THREAD(); @@ -3760,6 +3799,20 @@ namespace graphene { namespace net { namespace detail { wlog( "Exception thrown while terminating Fetch updated peer lists loop, ignoring" ); } + try + { + _update_seed_nodes_loop_done.cancel_and_wait("node_impl::close()"); + dlog("Update seed nodes loop terminated"); + } + catch ( const fc::exception& e ) + { + wlog( "Exception thrown while terminating Update seed nodes loop, ignoring: ${e}", ("e", e) ); + } + catch (...) + { + wlog( "Exception thrown while terminating Update seed nodes loop, ignoring" ); + } + try { _bandwidth_monitor_loop_done.cancel_and_wait("node_impl::close()"); @@ -4164,6 +4217,7 @@ namespace graphene { namespace net { namespace detail { assert(!_accept_loop_complete.valid() && !_p2p_network_connect_loop_done.valid() && + !_update_seed_nodes_loop_done.valid() && !_fetch_sync_items_loop_done.valid() && !_fetch_item_loop_done.valid() && !_advertise_inventory_loop_done.valid() && @@ -4181,6 +4235,7 @@ namespace graphene { namespace net { namespace detail { _fetch_updated_peer_lists_loop_done = fc::async([=](){ fetch_updated_peer_lists_loop(); }, "fetch_updated_peer_lists_loop"); _bandwidth_monitor_loop_done = fc::async([=](){ bandwidth_monitor_loop(); }, "bandwidth_monitor_loop"); _dump_node_status_task_done = fc::async([=](){ dump_node_status_task(); }, "dump_node_status_task"); + schedule_next_update_seed_nodes_task(); } void node_impl::add_node(const fc::ip::endpoint& ep) @@ -4198,6 +4253,33 @@ namespace graphene { namespace net { namespace detail { trigger_p2p_network_connect_loop(); } + void node_impl::add_seed_node(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + _seed_nodes.insert( endpoint_string ); + resolve_seed_node_and_add( endpoint_string ); + } + + void node_impl::resolve_seed_node_and_add(const std::string& endpoint_string) + { + VERIFY_CORRECT_THREAD(); + std::vector endpoints; + ilog("Resolving seed node ${endpoint}", ("endpoint", endpoint_string)); + try + { + endpoints = graphene::net::node::resolve_string_to_ip_endpoints(endpoint_string); + } + catch(...) + { + wlog( "Unable to resolve endpoint during attempt to add seed node ${ep}", ("ep", endpoint_string) ); + } + for (const fc::ip::endpoint& endpoint : endpoints) + { + ilog("Adding seed node ${endpoint}", ("endpoint", endpoint)); + add_node(endpoint); + } + } + void node_impl::initiate_connect_to(const peer_connection_ptr& new_peer) { new_peer->get_socket().open(); @@ -5104,4 +5186,52 @@ namespace graphene { namespace net { namespace detail { } // end namespace detail + // TODO move this function to impl class + std::vector node::resolve_string_to_ip_endpoints(const std::string& in) + { + try + { + std::string::size_type colon_pos = in.find(':'); + if (colon_pos == std::string::npos) + FC_THROW("Missing required port number in endpoint string \"${endpoint_string}\"", + ("endpoint_string", in)); + std::string port_string = in.substr(colon_pos + 1); + try + { + uint16_t port = boost::lexical_cast(port_string); + + std::string hostname = in.substr(0, colon_pos); + std::vector endpoints = fc::resolve(hostname, port); + if (endpoints.empty()) + FC_THROW_EXCEPTION( fc::unknown_host_exception, + "The host name can not be resolved: ${hostname}", + ("hostname", hostname) ); + return endpoints; + } + catch (const boost::bad_lexical_cast&) + { + FC_THROW("Bad port: ${port}", ("port", port_string)); + } + } + FC_CAPTURE_AND_RETHROW((in)) + } + + void node::add_seed_nodes(std::vector seeds) + { + for(const std::string& endpoint_string : seeds ) + { + try { + add_seed_node(endpoint_string); + } catch( const fc::exception& e ) { + wlog( "caught exception ${e} while adding seed node ${endpoint}", + ("e", e.to_detail_string())("endpoint", endpoint_string) ); + } + } + } + + void node::add_seed_node(const std::string& in) + { + INVOKE_IN_IMPL(add_seed_node, in); + } + } } // end namespace graphene::net diff --git a/libraries/net/node_impl.hxx b/libraries/net/node_impl.hxx index 7d31d16eea..da12718b98 100644 --- a/libraries/net/node_impl.hxx +++ b/libraries/net/node_impl.hxx @@ -337,6 +337,14 @@ class node_impl : public peer_connection_delegate std::list > _handle_message_calls_in_progress; + /// used by the task that checks whether addresses of seed nodes have been updated + // @{ + boost::container::flat_set _seed_nodes; + fc::future _update_seed_nodes_loop_done; + void update_seed_nodes_task(); + void schedule_next_update_seed_nodes_task(); + // @} + node_impl(const std::string& user_agent); virtual ~node_impl(); @@ -482,6 +490,8 @@ class node_impl : public peer_connection_delegate void listen_to_p2p_network(); void connect_to_p2p_network(); void add_node( const fc::ip::endpoint& ep ); + void add_seed_node( const std::string& seed_string ); + void resolve_seed_node_and_add( const std::string& seed_string ); void initiate_connect_to(const peer_connection_ptr& peer); void connect_to_endpoint(const fc::ip::endpoint& ep); void listen_on_endpoint(const fc::ip::endpoint& ep , bool wait_if_not_available); diff --git a/libraries/protocol/asset_ops.cpp b/libraries/protocol/asset_ops.cpp index 09198b0a72..7bfd65ef00 100644 --- a/libraries/protocol/asset_ops.cpp +++ b/libraries/protocol/asset_ops.cpp @@ -257,6 +257,8 @@ void asset_options::validate()const void asset_claim_fees_operation::validate()const { FC_ASSERT( fee.amount >= 0 ); FC_ASSERT( amount_to_claim.amount > 0 ); + if( extensions.value.claim_from_asset_id.valid() ) + FC_ASSERT( *extensions.value.claim_from_asset_id != amount_to_claim.asset_id ); } void asset_claim_pool_operation::validate()const { @@ -278,6 +280,7 @@ GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_oper GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) diff --git a/libraries/protocol/chain_parameters.cpp b/libraries/protocol/chain_parameters.cpp index 860bf04754..1169224f41 100644 --- a/libraries/protocol/chain_parameters.cpp +++ b/libraries/protocol/chain_parameters.cpp @@ -103,8 +103,8 @@ namespace graphene { namespace protocol { "Committee proposal review period must be less than the maximum proposal lifetime" ); if( extensions.value.market_fee_network_percent.valid() ) { - FC_ASSERT( *extensions.value.market_fee_network_percent <= GRAPHENE_100_PERCENT, - "The market_fee_network_percent parameter can not exceed 100%" ); + FC_ASSERT( *extensions.value.market_fee_network_percent <= 3000, // GRAPHENE_100_PERCENT is 10000 + "The market_fee_network_percent parameter can not exceed 30%" ); } if( extensions.value.maker_fee_discount_percent.valid() ) { diff --git a/libraries/protocol/fee_schedule_calc.cpp b/libraries/protocol/fee_schedule_calc.cpp index 8c0900d75f..6b21890d1d 100644 --- a/libraries/protocol/fee_schedule_calc.cpp +++ b/libraries/protocol/fee_schedule_calc.cpp @@ -53,6 +53,16 @@ namespace graphene { namespace protocol { } }; + template<> + uint64_t calc_fee_visitor::operator()(const htlc_create_operation& op)const + { + //TODO: refactor for performance (see https://github.com/bitshares/bitshares-core/issues/2150) + transfer_operation::fee_parameters_type t; + if (param.exists()) + t = param.get(); + return op.calculate_fee( param.get(), t.price_per_kbyte).value; + } + asset fee_schedule::calculate_fee( const operation& op )const { uint64_t required_fee = op.visit( calc_fee_visitor( *this, op ) ); diff --git a/libraries/protocol/htlc.cpp b/libraries/protocol/htlc.cpp index b7fe9ef4a0..5694ec5a26 100644 --- a/libraries/protocol/htlc.cpp +++ b/libraries/protocol/htlc.cpp @@ -34,13 +34,16 @@ namespace graphene { namespace protocol { FC_ASSERT( amount.amount > 0, "HTLC amount should be greater than zero" ); } - share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params )const + share_type htlc_create_operation::calculate_fee( const fee_parameters_type& fee_params, + uint32_t fee_per_kb )const { uint64_t days = ( claim_period_seconds + SECONDS_PER_DAY - 1 ) / SECONDS_PER_DAY; // multiply with overflow check - uint64_t per_day_fee = fee_params.fee_per_day * days; - FC_ASSERT( days == 0 || per_day_fee / days == fee_params.fee_per_day, "Fee calculation overflow" ); - return fee_params.fee + per_day_fee; + share_type total_fee = fee_params.fee; + total_fee += share_type(fee_params.fee_per_day) * days; + if (extensions.value.memo.valid()) + total_fee += calculate_data_fee( fc::raw::pack_size(extensions.value.memo), fee_per_kb); + return total_fee; } void htlc_redeem_operation::validate()const { @@ -69,6 +72,7 @@ namespace graphene { namespace protocol { } } GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) GRAPHENE_IMPLEMENT_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) diff --git a/libraries/protocol/include/graphene/protocol/asset_ops.hpp b/libraries/protocol/include/graphene/protocol/asset_ops.hpp index d61bb53122..cc24c17f6e 100644 --- a/libraries/protocol/include/graphene/protocol/asset_ops.hpp +++ b/libraries/protocol/include/graphene/protocol/asset_ops.hpp @@ -456,10 +456,21 @@ namespace graphene { namespace protocol { uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + struct additional_options_type + { + /// Which asset to claim fees from. This is needed, e.g., to claim collateral- + /// denominated fees from a collateral-backed smart asset. If unset, assumed to be same + /// asset as amount_to_claim is denominated in, such as would be the case when claiming + /// market fees. If set, validation requires it to be a different asset_id than + /// amount_to_claim (else there would exist two ways to form the same request). + fc::optional claim_from_asset_id; + }; + asset fee; - account_id_type issuer; - asset amount_to_claim; /// amount_to_claim.asset_id->issuer must == issuer - extensions_type extensions; + account_id_type issuer; ///< must match issuer of asset from which we claim fees + asset amount_to_claim; + + extension extensions; account_id_type fee_payer()const { return issuer; } void validate()const; @@ -531,6 +542,8 @@ namespace graphene { namespace protocol { FC_REFLECT( graphene::protocol::asset_claim_fees_operation, (fee)(issuer)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::protocol::asset_claim_fees_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::protocol::asset_claim_fees_operation::additional_options_type, (claim_from_asset_id) ) + FC_REFLECT( graphene::protocol::asset_claim_pool_operation, (fee)(issuer)(asset_id)(amount_to_claim)(extensions) ) FC_REFLECT( graphene::protocol::asset_claim_pool_operation::fee_parameters_type, (fee) ) @@ -636,6 +649,7 @@ GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_settle_operat GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_fund_fee_pool_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_pool_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_claim_fees_operation::additional_options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_issuer_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::asset_update_bitasset_operation::fee_parameters_type ) diff --git a/libraries/protocol/include/graphene/protocol/htlc.hpp b/libraries/protocol/include/graphene/protocol/htlc.hpp index 9c8058be16..606b2f383f 100644 --- a/libraries/protocol/include/graphene/protocol/htlc.hpp +++ b/libraries/protocol/include/graphene/protocol/htlc.hpp @@ -26,17 +26,20 @@ #include #include #include +#include #include // std::max namespace graphene { namespace protocol { typedef fc::ripemd160 htlc_algo_ripemd160; typedef fc::sha1 htlc_algo_sha1; typedef fc::sha256 htlc_algo_sha256; + typedef fc::hash160 htlc_algo_hash160; typedef fc::static_variant< htlc_algo_ripemd160, htlc_algo_sha1, - htlc_algo_sha256 + htlc_algo_sha256, + htlc_algo_hash160 > htlc_hash; struct htlc_create_operation : public base_operation @@ -45,6 +48,7 @@ namespace graphene { namespace protocol { uint64_t fee = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; uint64_t fee_per_day = 1 * GRAPHENE_BLOCKCHAIN_PRECISION; }; + // paid to network asset fee; // where the held monies are to come from @@ -59,8 +63,13 @@ namespace graphene { namespace protocol { uint16_t preimage_size; // The time the funds will be returned to the source if not claimed uint32_t claim_period_seconds; - // for future expansion - extensions_type extensions; + + // additional extensions + struct additional_options_type + { + fc::optional memo; + }; + extension extensions; /*** * @brief Does simple validation of this object @@ -75,7 +84,7 @@ namespace graphene { namespace protocol { /**** * @brief calculates the fee to be paid for this operation */ - share_type calculate_fee(const fee_parameters_type& fee_params)const; + share_type calculate_fee(const fee_parameters_type& fee_params, uint32_t fee_per_kb)const; }; struct htlc_redeem_operation : public base_operation @@ -121,9 +130,10 @@ namespace graphene { namespace protocol { htlc_redeemed_operation() {} htlc_redeemed_operation( htlc_id_type htlc_id, account_id_type from, account_id_type to, - account_id_type redeemer, asset amount, const htlc_hash& preimage_hash, uint16_t preimage_size ) : + account_id_type redeemer, asset amount, const htlc_hash& preimage_hash, uint16_t preimage_size, + const std::vector& preimage ) : htlc_id(htlc_id), from(from), to(to), redeemer(redeemer), amount(amount), - htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size) {} + htlc_preimage_hash(preimage_hash), htlc_preimage_size(preimage_size), preimage(preimage) {} account_id_type fee_payer()const { return to; } void validate()const { FC_ASSERT( !"virtual operation" ); } @@ -137,6 +147,7 @@ namespace graphene { namespace protocol { uint16_t htlc_preimage_size; asset fee; + std::vector preimage; }; struct htlc_extend_operation : public base_operation @@ -206,6 +217,7 @@ namespace graphene { namespace protocol { FC_REFLECT_TYPENAME( graphene::protocol::htlc_hash ) FC_REFLECT( graphene::protocol::htlc_create_operation::fee_parameters_type, (fee) (fee_per_day) ) +FC_REFLECT( graphene::protocol::htlc_create_operation::additional_options_type, (memo)) FC_REFLECT( graphene::protocol::htlc_redeem_operation::fee_parameters_type, (fee) (fee_per_kb) ) FC_REFLECT( graphene::protocol::htlc_redeemed_operation::fee_parameters_type, ) // VIRTUAL FC_REFLECT( graphene::protocol::htlc_extend_operation::fee_parameters_type, (fee) (fee_per_day)) @@ -215,12 +227,13 @@ FC_REFLECT( graphene::protocol::htlc_create_operation, (fee)(from)(to)(amount)(preimage_hash)(preimage_size)(claim_period_seconds)(extensions)) FC_REFLECT( graphene::protocol::htlc_redeem_operation, (fee)(htlc_id)(redeemer)(preimage)(extensions)) FC_REFLECT( graphene::protocol::htlc_redeemed_operation, - (fee)(htlc_id)(from)(to)(redeemer)(amount)(htlc_preimage_hash)(htlc_preimage_size)) + (fee)(htlc_id)(from)(to)(redeemer)(amount)(htlc_preimage_hash)(htlc_preimage_size)(preimage)) FC_REFLECT( graphene::protocol::htlc_extend_operation, (fee)(htlc_id)(update_issuer)(seconds_to_add)(extensions)) FC_REFLECT( graphene::protocol::htlc_refund_operation, (fee)(htlc_id)(to)(original_htlc_recipient)(htlc_amount)(htlc_preimage_hash)(htlc_preimage_size)) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::fee_parameters_type ) +GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation::additional_options_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_redeem_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_extend_operation::fee_parameters_type ) GRAPHENE_DECLARE_EXTERNAL_SERIALIZATION( graphene::protocol::htlc_create_operation ) diff --git a/libraries/protocol/include/graphene/protocol/types.hpp b/libraries/protocol/include/graphene/protocol/types.hpp index 2134f3b772..567a1230bb 100644 --- a/libraries/protocol/include/graphene/protocol/types.hpp +++ b/libraries/protocol/include/graphene/protocol/types.hpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 27ca70e6c7..90cc6fd2c2 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1446,12 +1446,13 @@ class wallet_api * @param preimage_hash the hash of the preimage * @param preimage_size the size of the preimage in bytes * @param claim_period_seconds how long after creation until the lock expires + * @param memo the memo * @param broadcast true if you wish to broadcast the transaction * @return the signed transaction */ signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast = false ); + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast = false ); /**** * Update a hashed time lock contract diff --git a/libraries/wallet/operation_printer.cpp b/libraries/wallet/operation_printer.cpp index 0d948e023c..d3f950c4e9 100644 --- a/libraries/wallet/operation_printer.cpp +++ b/libraries/wallet/operation_printer.cpp @@ -47,6 +47,10 @@ class htlc_hash_to_string_visitor { return "SHA256 " + hash.str(); } + result_type operator()( const fc::hash160& hash )const + { + return "HASH160 " + hash.str(); + } }; std::string operation_printer::fee(const graphene::protocol::asset& a)const { @@ -54,6 +58,64 @@ std::string operation_printer::fee(const graphene::protocol::asset& a)const { return ""; } +string operation_printer::print_memo( const fc::optional& memo )const +{ + std::string outstr; + if( memo ) + { + if( wallet.is_locked() ) + { + out << " -- Unlock wallet to see memo."; + } else { + try { + FC_ASSERT( wallet._keys.count(memo->to) || wallet._keys.count(memo->from), + "Memo is encrypted to a key ${to} or ${from} not in this wallet.", + ("to", memo->to)("from",memo->from) ); + if( wallet._keys.count(memo->to) ) { + auto my_key = wif_to_key(wallet._keys.at(memo->to)); + FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); + outstr = memo->get_message(*my_key, memo->from); + out << " -- Memo: " << outstr; + } else { + auto my_key = wif_to_key(wallet._keys.at(memo->from)); + FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); + outstr = memo->get_message(*my_key, memo->to); + out << " -- Memo: " << outstr; + } + } catch (const fc::exception& e) { + out << " -- could not decrypt memo"; + } + } + } + return outstr; +} + +void operation_printer::print_preimage(const std::vector& preimage)const +{ + if (preimage.size() == 0) + return; + out << " with preimage \""; + // cut it at 300 bytes max + auto flags = out.flags(); + out << std::hex << setw(2) << setfill('0'); + for (size_t i = 0; i < std::min(300, preimage.size()); i++) + out << +preimage[i]; + out.flags(flags); + if (preimage.size() > 300) + out << "...(truncated due to size)"; + out << "\""; +} + +string operation_printer::print_redeem(const graphene::protocol::htlc_id_type& id, + const std::string& redeemer, const std::vector& preimage, + const graphene::protocol::asset& op_fee)const +{ + out << redeemer << " redeemed HTLC with id " + << std::string( static_cast(id)); + print_preimage( preimage ); + return fee(op_fee); +} + std::string operation_printer::operator()(const transfer_from_blind_operation& op)const { auto a = wallet.get_asset( op.fee.asset_id ); @@ -75,37 +137,12 @@ std::string operation_printer::operator()(const transfer_to_blind_operation& op) << " fee: " << fa.amount_to_pretty_string( op.fee ); return ""; } + string operation_printer::operator()(const transfer_operation& op) const { out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount) << " from " << wallet.get_account(op.from).name << " to " << wallet.get_account(op.to).name; - std::string memo; - if( op.memo ) - { - if( wallet.is_locked() ) - { - out << " -- Unlock wallet to see memo."; - } else { - try { - FC_ASSERT( wallet._keys.count(op.memo->to) || wallet._keys.count(op.memo->from), - "Memo is encrypted to a key ${to} or ${from} not in this wallet.", - ("to", op.memo->to)("from",op.memo->from) ); - if( wallet._keys.count(op.memo->to) ) { - auto my_key = wif_to_key(wallet._keys.at(op.memo->to)); - FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); - memo = op.memo->get_message(*my_key, op.memo->from); - out << " -- Memo: " << memo; - } else { - auto my_key = wif_to_key(wallet._keys.at(op.memo->from)); - FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted."); - memo = op.memo->get_message(*my_key, op.memo->to); - out << " -- Memo: " << memo; - } - } catch (const fc::exception& e) { - out << " -- could not decrypt memo"; - } - } - } + std::string memo = print_memo( op.memo ); fee(op.fee); return memo; } @@ -135,15 +172,12 @@ std::string operation_printer::operator()(const asset_create_operation& op) cons std::string operation_printer::operator()(const htlc_redeem_operation& op) const { - out << "Redeem HTLC with database id " - << std::to_string(op.htlc_id.space_id) - << "." << std::to_string(op.htlc_id.type_id) - << "." << std::to_string((uint64_t)op.htlc_id.instance) - << " with preimage \""; - for (unsigned char c : op.preimage) - out << c; - out << "\""; - return fee(op.fee); + return print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee); +} + +std::string operation_printer::operator()(const htlc_redeemed_operation& op) const +{ + return print_redeem(op.htlc_id, wallet.get_account(op.redeemer).name, op.preimage, op.fee); } std::string operation_printer::operator()(const htlc_create_operation& op) const @@ -152,20 +186,19 @@ std::string operation_printer::operator()(const htlc_create_operation& op) const auto fee_asset = wallet.get_asset( op.fee.asset_id ); auto to = wallet.get_account( op.to ); + auto from = wallet.get_account( op.from ); operation_result_printer rprinter(wallet); std::string database_id = result.visit(rprinter); - out << "Create HTLC to " << to.name + out << "Create HTLC from " << from.name << " to " << to.name << " with id " << database_id - << " preimage hash: [" - << op.preimage_hash.visit( vtor ) - << "] (Fee: " << fee_asset.amount_to_pretty_string( op.fee ) << ")"; + << " preimage hash: [" << op.preimage_hash.visit( vtor ) << "] "; + print_memo( op.extensions.value.memo ); // determine if the block that the HTLC is in is before or after LIB int32_t pending_blocks = hist.block_num - wallet.get_dynamic_global_properties().last_irreversible_block_num; if (pending_blocks > 0) out << " (pending " << std::to_string(pending_blocks) << " blocks)"; - - return ""; + return fee(op.fee); } std::string operation_result_printer::operator()(const void_result& x) const diff --git a/libraries/wallet/operation_printer.hpp b/libraries/wallet/operation_printer.hpp index 9ff09586cf..428ca469d1 100644 --- a/libraries/wallet/operation_printer.hpp +++ b/libraries/wallet/operation_printer.hpp @@ -98,6 +98,13 @@ struct operation_printer std::string operator()(const graphene::protocol::asset_create_operation& op)const; std::string operator()(const graphene::protocol::htlc_create_operation& op)const; std::string operator()(const graphene::protocol::htlc_redeem_operation& op)const; + std::string operator()(const graphene::protocol::htlc_redeemed_operation& op)const; + protected: + std::string print_memo( const fc::optional& memo)const; + void print_preimage( const std::vector& preimage)const; + std::string print_redeem(const graphene::protocol::htlc_id_type& id, + const std::string& redeemer, const std::vector& preimage, + const graphene::protocol::asset& op_fee)const; }; }}} // namespace graphene::wallet::detail diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index ea2b6556e5..8e1ca9f840 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -202,10 +202,10 @@ uint64_t wallet_api::get_asset_count()const signed_transaction wallet_api::htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast) + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast) { return my->htlc_create(source, destination, amount, asset_symbol, hash_algorithm, preimage_hash, preimage_size, - claim_period_seconds, broadcast); + claim_period_seconds, memo, broadcast); } fc::optional wallet_api::get_htlc(std::string htlc_id) const @@ -223,6 +223,8 @@ fc::optional wallet_api::get_htlc(std::string htlc_id) const const auto& asset = my->get_asset( obj.transfer.asset_id ); transfer["asset"] = asset.symbol; transfer["amount"] = graphene::app::uint128_amount_to_string( obj.transfer.amount.value, asset.precision ); + if (obj.memo.valid()) + transfer["memo"] = my->read_memo( *obj.memo ); class htlc_hash_to_variant_visitor { public: @@ -234,6 +236,8 @@ fc::optional wallet_api::get_htlc(std::string htlc_id) const { return convert("SHA1", obj.str()); } result_type operator()(const fc::sha256& obj)const { return convert("SHA256", obj.str()); } + result_type operator()(const fc::hash160& obj)const + { return convert("HASH160", obj.str()); } private: result_type convert(const std::string& type, const std::string& hash)const { diff --git a/libraries/wallet/wallet_api_impl.hpp b/libraries/wallet/wallet_api_impl.hpp index 465c071f0b..2ee1de89d8 100644 --- a/libraries/wallet/wallet_api_impl.hpp +++ b/libraries/wallet/wallet_api_impl.hpp @@ -220,8 +220,7 @@ class wallet_api_impl transaction preview_builder_transaction(transaction_handle_type handle); signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true); signed_transaction sign_builder_transaction2(transaction_handle_type transaction_handle, - const vector& signing_keys = vector(), - bool broadcast = true); + const vector& signing_keys = vector(), bool broadcast = true); pair broadcast_transaction(signed_transaction tx); @@ -300,9 +299,10 @@ class wallet_api_impl signed_transaction htlc_create( string source, string destination, string amount, string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast = false ); + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast = false ); - signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, bool broadcast ); + signed_transaction htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, + bool broadcast ); signed_transaction htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast); diff --git a/libraries/wallet/wallet_transfer.cpp b/libraries/wallet/wallet_transfer.cpp index 7228cec5dc..57a8ae4960 100644 --- a/libraries/wallet/wallet_transfer.cpp +++ b/libraries/wallet/wallet_transfer.cpp @@ -43,6 +43,8 @@ namespace graphene { namespace wallet { namespace detail { return fc::sha256( hash ); if( name_upper == "SHA1" ) return fc::sha1( hash ); + if( name_upper == "HASH160" ) + return fc::hash160( hash ); FC_THROW_EXCEPTION( fc::invalid_arg_exception, "Unknown algorithm '${a}'", ("a",algorithm) ); } @@ -81,9 +83,9 @@ namespace graphene { namespace wallet { namespace detail { return sign_transaction(tx, broadcast); } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_create( string source, string destination, string amount, string asset_symbol, - string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, - const uint32_t claim_period_seconds, bool broadcast ) + signed_transaction wallet_api_impl::htlc_create( string source, string destination, string amount, + string asset_symbol, string hash_algorithm, const std::string& preimage_hash, uint32_t preimage_size, + const uint32_t claim_period_seconds, const std::string& memo, bool broadcast ) { try { @@ -91,6 +93,8 @@ namespace graphene { namespace wallet { namespace detail { fc::optional asset_obj = get_asset(asset_symbol); FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol)); + const account_object& from_acct = get_account(source); + const account_object& to_acct = get_account(destination); htlc_create_operation create_op; create_op.from = get_account(source).id; create_op.to = get_account(destination).id; @@ -98,6 +102,15 @@ namespace graphene { namespace wallet { namespace detail { create_op.claim_period_seconds = claim_period_seconds; create_op.preimage_hash = do_hash( hash_algorithm, preimage_hash ); create_op.preimage_size = preimage_size; + if (!memo.empty()) + { + memo_data data; + data.from = from_acct.options.memo_key; + data.to = to_acct.options.memo_key; + data.set_message( + get_private_key(from_acct.options.memo_key), to_acct.options.memo_key, memo); + create_op.extensions.value.memo = data; + } signed_transaction tx; tx.operations.push_back(create_op); @@ -109,8 +122,8 @@ namespace graphene { namespace wallet { namespace detail { (preimage_hash)(preimage_size)(claim_period_seconds)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_redeem( string htlc_id, string issuer, const std::vector& preimage, - bool broadcast ) + signed_transaction wallet_api_impl::htlc_redeem( string htlc_id, string issuer, + const std::vector& preimage, bool broadcast ) { try { @@ -134,7 +147,7 @@ namespace graphene { namespace wallet { namespace detail { } FC_CAPTURE_AND_RETHROW( (htlc_id)(issuer)(preimage)(broadcast) ) } - signed_transaction wallet_api_impl::htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, + signed_transaction wallet_api_impl::htlc_extend ( string htlc_id, string issuer, const uint32_t seconds_to_add, bool broadcast) { try diff --git a/programs/build_helpers/member_enumerator.cpp b/programs/build_helpers/member_enumerator.cpp index 6a292298ee..830878e314 100644 --- a/programs/build_helpers/member_enumerator.cpp +++ b/programs/build_helpers/member_enumerator.cpp @@ -103,7 +103,7 @@ void class_processor::process_class( const static_variant< T... >* dummy ) static_variant dummy2; static_variant_visitor vtor( this ); - for( int w=0; w, false > { init = true; fc::static_variant var; - for( int i = 0; i < var.count(); ++i ) + for( size_t i = 0; i < var.count(); ++i ) { var.set_which(i); var.visit( register_type_visitor() ); @@ -372,7 +372,7 @@ int main( int argc, char** argv ) operation op; std::cout << "ChainTypes.operations=\n"; - for( int i = 0; i < op.count(); ++i ) + for( size_t i = 0; i < op.count(); ++i ) { op.set_which(i); op.visit( detail_ns::serialize_type_visitor(i) ); diff --git a/programs/size_checker/main.cpp b/programs/size_checker/main.cpp index a7c09308fc..e283e2d6e6 100644 --- a/programs/size_checker/main.cpp +++ b/programs/size_checker/main.cpp @@ -85,7 +85,7 @@ int main( int argc, char** argv ) idump( (witnesses) ); - for( int32_t i = 0; i < op.count(); ++i ) + for( size_t i = 0; i < op.count(); ++i ) { op.set_which(i); op.visit( size_check_type_visitor(i) ); diff --git a/tests/cli/main.cpp b/tests/cli/main.cpp index c51c69c56e..0615f8dff6 100644 --- a/tests/cli/main.cpp +++ b/tests/cli/main.cpp @@ -423,7 +423,7 @@ BOOST_FIXTURE_TEST_CASE( create_new_account, cli_fixture ) BOOST_CHECK(con.wallet_api_ptr->import_key("jmjatlanta", bki.wif_priv_key)); con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give jmjatlanta some bitsahres + // attempt to give jmjatlanta some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to jmjatlanta"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer( "nathan", "jmjatlanta", "10000", "1.3.0", "Here are some CORE token for your new account", true @@ -848,7 +848,7 @@ BOOST_FIXTURE_TEST_CASE( account_history_pagination, cli_fixture ) { INVOKE(create_new_account); - // attempt to give jmjatlanta some bitsahres + // attempt to give jmjatlanta some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to jmjatlanta"); for(int i = 1; i <= 199; i++) { @@ -945,7 +945,7 @@ BOOST_AUTO_TEST_CASE( cli_multisig_transaction ) create_multisig_acct_tx.operations.push_back(account_create_op); con.wallet_api_ptr->sign_transaction(create_multisig_acct_tx, true); - // attempt to give cifer.test some bitsahres + // attempt to give cifer.test some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to cifer.test"); signed_transaction transfer_tx1 = con.wallet_api_ptr->transfer("nathan", "cifer.test", "10000", "1.3.0", "Here are some BTS for your new account", true); @@ -1126,10 +1126,8 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(!bki.brain_priv_key.empty()); signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "alice", "nathan", "nathan", true); - // save the private key for this new account in the wallet file - BOOST_CHECK(con.wallet_api_ptr->import_key("alice", bki.wif_priv_key)); con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give alice some bitsahres + // attempt to give alice some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", "Here are some CORE token for your new account", true); @@ -1141,17 +1139,15 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) BOOST_CHECK(!bki.brain_priv_key.empty()); signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, "bob", "nathan", "nathan", true); - // save the private key for this new account in the wallet file - BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bki.wif_priv_key)); - con.wallet_api_ptr->save_wallet_file(con.wallet_filename); - // attempt to give bob some bitsahres + // this should cause resync which will import the keys of alice and bob + generate_block(app1); + // attempt to give bob some bitshares BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", "Here are some CORE token for your new account", true); con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); } - BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); // create an HTLC std::string preimage_string = "My Secret"; @@ -1168,7 +1164,7 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) uint32_t timelock = fc::days(1).to_seconds(); graphene::chain::signed_transaction result_tx = con.wallet_api_ptr->htlc_create("alice", "bob", - "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, true); + "3", "1.3.0", "SHA256", hash_str, preimage_string.size(), timelock, "", true); // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: std::string alice_htlc_id_as_string; @@ -1190,7 +1186,7 @@ BOOST_AUTO_TEST_CASE( cli_create_htlc ) // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC con.wallet_api_ptr->htlc_create("bob", "alice", - "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, true); + "3", "BOBCOIN", "SHA256", hash_str, preimage_string.size(), timelock, "", true); // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: std::string bob_htlc_id_as_string; @@ -1593,3 +1589,233 @@ BOOST_FIXTURE_TEST_CASE(cli_use_authorized_transfer, cli_fixture) { throw; } } + +BOOST_AUTO_TEST_CASE( cli_create_htlc_bsip64 ) +{ + using namespace graphene::chain; + using namespace graphene::app; + std::shared_ptr app1; + try { + fc::temp_directory app_dir( graphene::utilities::temp_directory_path() ); + + int server_port_number = 0; + app1 = start_application(app_dir, server_port_number); + // set committee parameters + app1->chain_database()->modify(app1->chain_database()->get_global_properties(), [](global_property_object& p) + { + graphene::chain::htlc_options params; + params.max_preimage_size = 1024; + params.max_timeout_secs = 60 * 60 * 24 * 28; + p.parameters.extensions.value.updatable_htlc_options = params; + }); + + // connect to the server + client_connection con(app1, app_dir, server_port_number); + + // get past hardforks + generate_blocks( app1, HARDFORK_CORE_BSIP64_TIME + 10); + + BOOST_TEST_MESSAGE("Setting wallet password"); + con.wallet_api_ptr->set_password("supersecret"); + con.wallet_api_ptr->unlock("supersecret"); + + // import Nathan account + BOOST_TEST_MESSAGE("Importing nathan key"); + std::vector nathan_keys{"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"}; + BOOST_CHECK_EQUAL(nathan_keys[0], "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3"); + BOOST_CHECK(con.wallet_api_ptr->import_key("nathan", nathan_keys[0])); + + BOOST_TEST_MESSAGE("Importing nathan's balance"); + std::vector import_txs = con.wallet_api_ptr->import_balance("nathan", nathan_keys, true); + account_object nathan_acct_before_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // upgrade nathan + BOOST_TEST_MESSAGE("Upgrading Nathan to LTM"); + signed_transaction upgrade_tx = con.wallet_api_ptr->upgrade_account("nathan", true); + account_object nathan_acct_after_upgrade = con.wallet_api_ptr->get_account("nathan"); + + // verify that the upgrade was successful + BOOST_CHECK_PREDICATE( std::not_equal_to(), + (nathan_acct_before_upgrade.membership_expiration_date.sec_since_epoch()) + (nathan_acct_after_upgrade.membership_expiration_date.sec_since_epoch()) ); + BOOST_CHECK(nathan_acct_after_upgrade.is_lifetime_member()); + + // Create new asset called BOBCOIN + try + { + graphene::chain::asset_options asset_ops; + asset_ops.max_supply = 1000000; + asset_ops.core_exchange_rate = price(asset(2),asset(1,asset_id_type(1))); + fc::optional bit_opts; + con.wallet_api_ptr->create_asset("nathan", "BOBCOIN", 5, asset_ops, bit_opts, true); + } + catch(exception& e) + { + BOOST_FAIL(e.what()); + } + catch(...) + { + BOOST_FAIL("Unknown exception creating BOBCOIN"); + } + + // create a new account for Alice + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, + "alice", "nathan", "nathan", true); + con.wallet_api_ptr->save_wallet_file(con.wallet_filename); + // attempt to give alice some bitshares + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to alice"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "alice", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + } + + // create a new account for Bob + { + graphene::wallet::brain_key_info bki = con.wallet_api_ptr->suggest_brain_key(); + BOOST_CHECK(!bki.brain_priv_key.empty()); + signed_transaction create_acct_tx = con.wallet_api_ptr->create_account_with_brain_key(bki.brain_priv_key, + "bob", "nathan", "nathan", true); + // this should cause resync which will import the keys of alice and bob + generate_block(app1); + // attempt to give bob some bitshares + BOOST_TEST_MESSAGE("Transferring bitshares from Nathan to Bob"); + signed_transaction transfer_tx = con.wallet_api_ptr->transfer("nathan", "bob", "10000", "1.3.0", + "Here are some CORE token for your new account", true); + con.wallet_api_ptr->issue_asset("bob", "5", "BOBCOIN", "Here are your BOBCOINs", true); + } + + BOOST_TEST_MESSAGE("Alice has agreed to buy 3 BOBCOIN from Bob for 3 BTS. Alice creates an HTLC"); + // create an HTLC + std::string preimage_string = "My Super Long Secret that is larger than 50 charaters. How do I look?\n"; + fc::hash160 preimage_md = fc::hash160::hash(preimage_string); + std::stringstream ss; + for(size_t i = 0; i < preimage_md.data_size(); i++) + { + char d = preimage_md.data()[i]; + unsigned char uc = static_cast(d); + ss << std::setfill('0') << std::setw(2) << std::hex << (int)uc; + } + std::string hash_str = ss.str(); + BOOST_TEST_MESSAGE("Secret is " + preimage_string + " and hash is " + hash_str); + uint32_t timelock = fc::days(1).to_seconds(); + graphene::chain::signed_transaction result_tx + = con.wallet_api_ptr->htlc_create("alice", "bob", + "3", "1.3.0", "HASH160", hash_str, preimage_string.size(), timelock, "Alice to Bob", true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string alice_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] + .operation_results[0].get(); + alice_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Alice shares the HTLC ID with Bob. The HTLC ID is: " + alice_htlc_id_as_string); + } + + // Bob can now look over Alice's HTLC, to see if it is what was agreed to. + BOOST_TEST_MESSAGE("Bob retrieves the HTLC Object by ID to examine it."); + auto alice_htlc = con.wallet_api_ptr->get_htlc(alice_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(alice_htlc)); + + // Bob likes what he sees, so he creates an HTLC, using the info he retrieved from Alice's HTLC + con.wallet_api_ptr->htlc_create("bob", "alice", + "3", "BOBCOIN", "HASH160", hash_str, preimage_string.size(), fc::hours(12).to_seconds(), + "Bob to Alice", true); + + // normally, a wallet would watch block production, and find the transaction. Here, we can cheat: + std::string bob_htlc_id_as_string; + { + BOOST_TEST_MESSAGE("The system is generating a block"); + graphene::chain::signed_block result_block; + BOOST_CHECK(generate_block(app1, result_block)); + + // get the ID: + htlc_id_type htlc_id = result_block.transactions[result_block.transactions.size()-1] + .operation_results[0].get(); + bob_htlc_id_as_string = (std::string)(object_id_type)htlc_id; + BOOST_TEST_MESSAGE("Bob shares the HTLC ID with Alice. The HTLC ID is: " + bob_htlc_id_as_string); + } + + // Alice can now look over Bob's HTLC, to see if it is what was agreed to: + BOOST_TEST_MESSAGE("Alice retrieves the HTLC Object by ID to examine it."); + auto bob_htlc = con.wallet_api_ptr->get_htlc(bob_htlc_id_as_string); + BOOST_TEST_MESSAGE("The HTLC Object is: " + fc::json::to_pretty_string(bob_htlc)); + + // Alice likes what she sees, so uses her preimage to get her BOBCOIN + { + BOOST_TEST_MESSAGE("Alice uses her preimage to retrieve the BOBCOIN"); + con.wallet_api_ptr->htlc_redeem(bob_htlc_id_as_string, "alice", preimage_string, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // Bob can look at Alice's history to see her preimage + { + BOOST_TEST_MESSAGE("Bob can look at the history of Alice to see the preimage"); + std::vector hist = con.wallet_api_ptr->get_account_history("alice", 1); + BOOST_CHECK( hist[0].description.find("with preimage \"4d792") != hist[0].description.npos); + } + + // Bob can also look at his own history to see Alice's preimage + { + BOOST_TEST_MESSAGE("Bob can look at his own history to see the preimage"); + std::vector hist = con.wallet_api_ptr->get_account_history("bob", 1); + BOOST_CHECK( hist[0].description.find("with preimage \"4d792") != hist[0].description.npos); + } + + // Bob can use the preimage to retrieve his BTS + { + BOOST_TEST_MESSAGE("Bob uses Alice's preimage to retrieve the BOBCOIN"); + con.wallet_api_ptr->htlc_redeem(alice_htlc_id_as_string, "bob", preimage_string, true); + BOOST_TEST_MESSAGE("The system is generating a block"); + BOOST_CHECK(generate_block(app1)); + } + + // test operation_printer + auto hist = con.wallet_api_ptr->get_account_history("alice", 10); + for(size_t i = 0; i < hist.size(); ++i) + { + auto obj = hist[i]; + std::stringstream ss; + ss << "Description: " << obj.description << "\n"; + auto str = ss.str(); + BOOST_TEST_MESSAGE( str ); + if (i == 3 || i == 4) + { + BOOST_CHECK( str.find("HASH160 620e4d5ba") != std::string::npos ); + } + } + con.wallet_api_ptr->lock(); + hist = con.wallet_api_ptr->get_account_history("alice", 10); + for(size_t i = 0; i < hist.size(); ++i) + { + auto obj = hist[i]; + std::stringstream ss; + ss << "Description: " << obj.description << "\n"; + auto str = ss.str(); + BOOST_TEST_MESSAGE( str ); + if (i == 3 || i == 4) + { + BOOST_CHECK( str.find("HASH160 620e4d5ba") != std::string::npos ); + } + } + con.wallet_api_ptr->unlock("supersecret"); + + // wait for everything to finish up + fc::usleep(fc::seconds(1)); + } catch( fc::exception& e ) { + edump((e.to_detail_string())); + throw; + } + app1->shutdown(); + app1.reset(); + // Intentional delay after app1->shutdown + std::cout << "cli_create_htlc conclusion: Intentional delay" << std::endl; + fc::usleep(fc::seconds(1)); +} diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index 72b54f8b1b..0958e5e331 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -530,6 +530,7 @@ void database_fixture::verify_asset_supplies( const database& db ) { const auto& bad = asset_obj.bitasset_data(db); total_balances[bad.options.short_backing_asset] += bad.settlement_fund; + total_balances[bad.options.short_backing_asset] += dasset_obj.accumulated_collateral_fees; } total_balances[asset_obj.id] += dasset_obj.confidential_supply.value; } @@ -1494,6 +1495,12 @@ flat_map< uint64_t, graphene::chain::fee_parameters > database_fixture::get_htlc extend_param.fee_per_day = 2 * GRAPHENE_BLOCKCHAIN_PRECISION; ret_val[((operation)htlc_extend_operation()).which()] = extend_param; + // set the transfer kb fee to something other than default, to verify we're looking + // at the correct fee + transfer_operation::fee_parameters_type transfer_param; + transfer_param.price_per_kbyte *= 2; + ret_val[ ((operation)transfer_operation()).which() ] = transfer_param; + return ret_val; } diff --git a/tests/tests/bsip86_tests.cpp b/tests/tests/bsip86_tests.cpp index bb90e6de37..739720e1ba 100644 --- a/tests/tests/bsip86_tests.cpp +++ b/tests/tests/bsip86_tests.cpp @@ -74,7 +74,7 @@ BOOST_AUTO_TEST_CASE( hardfork_time_test ) cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; committee_member_update_global_parameters_operation cmuop; - cmuop.new_parameters.extensions.value.market_fee_network_percent = 10001; // 100.01% + cmuop.new_parameters.extensions.value.market_fee_network_percent = 3001; // 30.01% cop.proposed_ops.emplace_back(cmuop); trx.operations.push_back(cop); diff --git a/tests/tests/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index fda3ab2fb9..1e27e051d1 100644 --- a/tests/tests/htlc_tests.cpp +++ b/tests/tests/htlc_tests.cpp @@ -63,7 +63,7 @@ void generate_random_preimage(uint16_t key_size, std::vector& vec) return; } -void advance_past_hardfork(database_fixture* db_fixture) +void advance_past_htlc_first_hardfork(database_fixture* db_fixture) { db_fixture->generate_blocks(HARDFORK_CORE_1468_TIME); set_expiration(db_fixture->db, db_fixture->trx); @@ -80,7 +80,7 @@ try { transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); uint16_t preimage_size = 256; std::vector pre_image(256); @@ -147,7 +147,8 @@ try { { graphene::chain::htlc_extend_operation big_extend; big_extend.htlc_id = alice_htlc_id; - big_extend.seconds_to_add = db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_timeout_secs + 10; + big_extend.seconds_to_add = db.get_global_properties().parameters.extensions.value + .updatable_htlc_options->max_timeout_secs + 10; big_extend.fee = db.get_global_properties().parameters.current_fees->calculate_fee(big_extend); big_extend.update_issuer = alice_id; trx.operations.push_back(big_extend); @@ -177,6 +178,347 @@ try { } FC_LOG_AND_RETHROW() } +/**** + * @brief helper to create htlc_create_operation + */ +htlc_create_operation create_htlc(const database& db, const account_id_type& from, const account_id_type& to, + const asset& amount, const graphene::protocol::htlc_hash& preimage_hash, uint16_t preimage_size, + uint64_t seconds, const fc::optional& memo = fc::optional()) +{ + htlc_create_operation ret_val; + ret_val.from = from; + ret_val.to = to; + ret_val.amount = amount; + ret_val.preimage_hash = preimage_hash; + ret_val.preimage_size = preimage_size; + ret_val.claim_period_seconds = seconds; + ret_val.extensions.value.memo = memo; + ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val); + return ret_val; +} + +/**** + * @brief helper to create a proposal + */ +proposal_create_operation create_proposal(const database& db, const account_id_type& from, + const htlc_create_operation& op, const fc::time_point_sec& expiration) +{ + proposal_create_operation ret_val; + ret_val.fee_paying_account = from; + ret_val.expiration_time = expiration; + ret_val.proposed_ops.emplace_back(op); + ret_val.fee = db.get_global_properties().parameters.current_fees->calculate_fee(ret_val); + return ret_val; +} + +BOOST_AUTO_TEST_CASE( htlc_hf_bsip64 ) +{ +try { + ACTORS((alice)(bob)(joker)); + + int64_t init_balance(100 * GRAPHENE_BLOCKCHAIN_PRECISION); + + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, joker_id, graphene::chain::asset(init_balance) ); + transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); + + advance_past_htlc_first_hardfork(this); + + uint16_t preimage_size = 256; + std::vector pre_image(256); + generate_random_preimage(preimage_size, pre_image); + + graphene::chain::htlc_id_type alice_htlc_id; + // cler everything out + generate_block(); + trx.clear(); + + /*** + * Proposals before the hardfork + */ + { + BOOST_TEST_MESSAGE( + "Alice is creating a proposal with an HTLC that contains a memo before the hardfork (should fail)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "memo"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with HASH160 (should fail)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "HASH160"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + /*** + * HTLC Contracts (non-proposals) before hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC that contains a memo before the hardfork (should fail)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "memo"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with HASH160 (should fail)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "HASH160"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + htlc_id_type htlc_id = PUSH_TX(db, trx, ~0).operation_results[0].get(); + trx.clear(); + BOOST_TEST_MESSAGE("Bob attempts to redeem, but can't because preimage size is 0 (should fail)"); + graphene::chain::htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = pre_image; + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Preimage size mismatch"); + trx.clear(); + } + + // Alice creates an asset + BOOST_TEST_MESSAGE("Create ALICECOIN so transfer_restricted can be controlled"); + const asset_id_type uia_id = create_user_issued_asset( "ALICECOIN", alice, transfer_restricted).id; + BOOST_TEST_MESSAGE("Issuing ALICECOIN to Bob"); + issue_uia(bob, asset(10000, uia_id) ); + // verify transfer restrictions are in place + REQUIRE_EXCEPTION_WITH_TEXT(transfer(bob, joker, asset(1, uia_id)), "transfer"); + trx.operations.clear(); + + { + BOOST_TEST_MESSAGE("Bob wants to transfer ALICECOIN, which is transfer_restricted (allowed before HF)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, + asset(3, uia_id), hash_it(pre_image), preimage_size, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE( + "Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id), + hash_it(pre_image), preimage_size, 60); + graphene::chain::proposal_create_operation prop_create = create_proposal( + db, bob_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop_create); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + + BOOST_TEST_MESSAGE("Fast Forwarding beyond HF BSIP64"); + generate_blocks( HARDFORK_CORE_BSIP64_TIME + 60 ); + set_expiration( db, trx ); + + /*** + * Proposals after the hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with an HTLC that contains a memo (should pass)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with HASH160 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating a proposal with a preimage size of 0 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + graphene::chain::proposal_create_operation prop1 = create_proposal( + db, alice_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop1); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + /*** + * HTLC Contracts (non-proposals) after hardfork + */ + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC that contains a memo after the hardfork (should pass)"); + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60, data); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Alice is creating an HTLC with HASH160 (should pass)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, alice_id, bob_id, + asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), hash_it(pre_image), 0, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("Bob wants to transfer ALICECOIN, which is transfer_restricted (no longer allowed)"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, + asset(3, uia_id), hash_it(pre_image), preimage_size, 60); + trx.operations.push_back(create_operation); + sign(trx, alice_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "transfer_restricted"); + trx.clear(); + } + { + BOOST_TEST_MESSAGE( + "Bob wants to transfer ALICECOIN within a proposal, always allowed, although will fail later"); + graphene::chain::htlc_create_operation create_operation = create_htlc(db, bob_id, joker_id, asset(3,uia_id), + hash_it(pre_image), preimage_size, 60); + graphene::chain::proposal_create_operation prop_create = create_proposal( + db, bob_id, create_operation, db.head_block_time() + 100); + trx.operations.push_back(prop_create); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + { + BOOST_TEST_MESSAGE("A memo field should include a charge per kb (uses fee from transfer_operation)"); + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3), hash_it(pre_image), + preimage_size, 60); + asset no_memo_fee = op.fee; + memo_data data; + data.from = alice_public_key; + data.to = bob_public_key; + data.set_message( alice_private_key, bob_public_key, "Dear Bob,\n\nMoney!\n\nLove, Alice"); + op.extensions.value.memo = data; + asset with_memo_fee = db.current_fee_schedule().calculate_fee(op); + BOOST_CHECK_GT( with_memo_fee.amount.value, no_memo_fee.amount.value ); + } + // After HF 64, a zero in the preimage_size means 2 things, no preimage (can happen, but who would do such a + // thing), or simply skip the size validation. To test, we must attempt to redeem both cases + { + // case 1: 0 preimage with 0 preimage_size + BOOST_TEST_MESSAGE("Attempt to create an HTLC with no preimage (should pass)"); + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), + hash_it(std::vector()), 0, 60); + trx.operations.push_back(op); + sign(trx, alice_private_key); + graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + htlc_id_type htlc_id = results.operation_results[0].get(); + BOOST_TEST_MESSAGE("Attempt to redeem HTLC that has no preimage, but include one anyway (should fail)"); + htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = pre_image; + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem HTLC that has no preimage (should pass)"); + redeem.preimage = std::vector(); + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + } + { + // case 2: a real preimage with 0 preimage size + htlc_create_operation op = create_htlc(db, alice_id, bob_id, asset(3 * GRAPHENE_BLOCKCHAIN_PRECISION), + hash_it(pre_image), 0, 60); + trx.operations.push_back(op); + sign(trx, alice_private_key); + graphene::protocol::processed_transaction results = PUSH_TX(db, trx, ~0); + trx.operations.clear(); + htlc_id_type htlc_id = results.operation_results[0].get(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage (should fail)"); + htlc_redeem_operation redeem; + redeem.htlc_id = htlc_id; + redeem.preimage = std::vector(); + redeem.redeemer = bob_id; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage size, but incorrect preimage"); + redeem.preimage = std::vector{'H','e','l','l','o'}; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + REQUIRE_EXCEPTION_WITH_TEXT(PUSH_TX(db, trx, ~0), "Provided preimage does not generate"); + trx.operations.clear(); + BOOST_TEST_MESSAGE("Attempt to redeem with no preimage size (should pass)"); + redeem.preimage = pre_image; + redeem.fee = db.current_fee_schedule().calculate_fee(redeem); + trx.operations.push_back(redeem); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + trx.operations.clear(); + } +} FC_LOG_AND_RETHROW() +} + BOOST_AUTO_TEST_CASE( htlc_fulfilled ) { try { @@ -188,7 +530,7 @@ try { transfer( committee_account, bob_id, graphene::chain::asset(init_balance) ); transfer( committee_account, joker_id, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); uint16_t preimage_size = 256; std::vector pre_image(preimage_size); @@ -271,7 +613,7 @@ try { BOOST_AUTO_TEST_CASE( other_peoples_money ) { try { - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); ACTORS((alice)(bob)); @@ -323,6 +665,9 @@ try { BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) { try { + ACTORS( (alice) ); + int64_t init_balance(10000 * GRAPHENE_BLOCKCHAIN_PRECISION); + transfer( committee_account, alice_id, graphene::chain::asset(init_balance) ); { // try to set committee parameters before hardfork proposal_create_operation cop = proposal_create_operation::committee_proposal( @@ -388,7 +733,7 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) // now things should start working... BOOST_TEST_MESSAGE("Advancing to HTLC hardfork time."); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); proposal_id_type good_proposal_id; BOOST_TEST_MESSAGE( "Creating a proposal to change the max_preimage_size to 2048 and set higher fees" ); @@ -411,7 +756,8 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) new_fee_schedule->parameters.insert( (*itr).second); } } - proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties().parameters, db.head_block_time()); + proposal_create_operation cop = proposal_create_operation::committee_proposal(db.get_global_properties() + .parameters, db.head_block_time()); cop.fee_paying_account = GRAPHENE_TEMP_ACCOUNT; cop.expiration_time = db.head_block_time() + *cop.review_period_seconds + 10; committee_member_update_global_parameters_operation uop; @@ -442,12 +788,14 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) } BOOST_TEST_MESSAGE( "Verifying that the parameters didn't change immediately" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + BOOST_CHECK_EQUAL( + db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); BOOST_TEST_MESSAGE( "Generating blocks until proposal expires" ); generate_blocks(good_proposal_id(db).expiration_time + 5); BOOST_TEST_MESSAGE( "Verify that the parameters still have not changed" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); + BOOST_CHECK_EQUAL(db.get_global_properties() + .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 19200u); BOOST_TEST_MESSAGE( "Generating blocks until next maintenance interval" ); generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); @@ -455,13 +803,17 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) BOOST_TEST_MESSAGE( "Verify that the change has been implemented" ); - BOOST_CHECK_EQUAL(db.get_global_properties().parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); - const graphene::chain::fee_schedule& current_fee_schedule = *(db.get_global_properties().parameters.current_fees); + BOOST_CHECK_EQUAL(db.get_global_properties() + .parameters.extensions.value.updatable_htlc_options->max_preimage_size, 2048u); + const graphene::chain::fee_schedule& current_fee_schedule = + *(db.get_global_properties().parameters.current_fees); const htlc_create_operation::fee_parameters_type& htlc_fee = current_fee_schedule.get(); BOOST_CHECK_EQUAL(htlc_fee.fee, 2 * GRAPHENE_BLOCKCHAIN_PRECISION); - -} FC_LOG_AND_RETHROW() } + + } + FC_LOG_AND_RETHROW() +} BOOST_AUTO_TEST_CASE( htlc_before_hardfork ) { try { @@ -562,13 +914,13 @@ BOOST_AUTO_TEST_CASE( fee_calculations ) htlc_create_operation create; // no days create.claim_period_seconds = 0; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 2 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 2 ); // exactly 1 day create.claim_period_seconds = 60 * 60 * 24; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 4 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 4 ); // tad over a day create.claim_period_seconds++; - BOOST_CHECK_EQUAL( create.calculate_fee(create_fee).value, 6 ); + BOOST_CHECK_EQUAL( create.calculate_fee(create_fee, 2).value, 6 ); } // redeem { @@ -633,7 +985,7 @@ try { fund( alice, graphene::chain::asset(init_balance) ); fund( bob, graphene::chain::asset(init_balance) ); - advance_past_hardfork(this); + advance_past_htlc_first_hardfork(this); // blacklist bob { @@ -924,6 +1276,4 @@ try { } } - - BOOST_AUTO_TEST_SUITE_END()