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/htlc_evaluator.cpp b/libraries/chain/htlc_evaluator.cpp index 008980b9b3..3b1238bc9d 100644 --- a/libraries/chain/htlc_evaluator.cpp +++ b/libraries/chain/htlc_evaluator.cpp @@ -29,6 +29,41 @@ 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) { @@ -51,6 +86,7 @@ namespace graphene { 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 +109,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 +137,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."); 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 a6a25d32b6..c02d879c66 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -29,7 +29,7 @@ namespace graphene { namespace chain { namespace detail { - void check_asset_options_hf_1774(const fc::time_point_sec& block_time, const asset_options& options); + 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); } @@ -89,6 +89,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" ); 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/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..06bc048dd0 100644 --- a/libraries/protocol/htlc.cpp +++ b/libraries/protocol/htlc.cpp @@ -34,13 +34,15 @@ 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 +71,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/htlc.hpp b/libraries/protocol/include/graphene/protocol/htlc.hpp index 9c8058be16..a5510ec092 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 @@ -137,6 +146,7 @@ namespace graphene { namespace protocol { uint16_t htlc_preimage_size; asset fee; + std::vector preimage; }; struct htlc_extend_operation : public base_operation @@ -206,6 +216,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 +226,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..975ac2f5b0 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 50 bytes max + auto flags = out.flags(); + out << std::hex << setw(2) << setfill('0'); + for (size_t i = 0; i < std::min(50, preimage.size()); i++) + out << +preimage[i]; + out.flags(flags); + if (preimage.size() > 50) + 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..6727723226 100644 --- a/libraries/wallet/wallet_api_impl.hpp +++ b/libraries/wallet/wallet_api_impl.hpp @@ -300,7 +300,7 @@ 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 ); diff --git a/libraries/wallet/wallet_transfer.cpp b/libraries/wallet/wallet_transfer.cpp index 7228cec5dc..0b41cefc41 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) ); } @@ -83,7 +85,7 @@ namespace graphene { namespace wallet { namespace detail { 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 ) + 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); 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..351be60e71 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,215 @@ 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)); + } + + // TODO: Bob can look at Alice's history to see her preimage + // 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 0ad8c1c0fb..7bab7a0703 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -1463,6 +1463,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/htlc_tests.cpp b/tests/tests/htlc_tests.cpp index fda3ab2fb9..0d693594f3 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); @@ -177,6 +177,341 @@ 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 +523,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 +606,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 +658,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 +726,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" ); @@ -460,8 +798,10 @@ BOOST_AUTO_TEST_CASE( htlc_hardfork_test ) 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 +902,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 +973,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 +1264,4 @@ try { } } - - BOOST_AUTO_TEST_SUITE_END()