diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index dff9626d..871f20bc 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -1186,6 +1186,21 @@ namespace graphene { namespace app { return result; } FC_CAPTURE_AND_RETHROW( (objs) ) } + vector database_api::get_vesting_balances( account_id_type account_id )const + { + try + { + vector result; + auto vesting_range = _db.get_index_type().indices().get().equal_range(account_id); + std::for_each(vesting_range.first, vesting_range.second, + [&result](const vesting_balance_object& balance) { + result.emplace_back(balance); + }); + return result; + } + FC_CAPTURE_AND_RETHROW( (account_id) ); + } + vector database_api::get_balance_objects( const vector
& addrs )const { try { const auto& bal_idx = _db.get_index_type(); diff --git a/libraries/app/include/graphene/app/api.hpp b/libraries/app/include/graphene/app/api.hpp index 82c82cae..649eea3f 100644 --- a/libraries/app/include/graphene/app/api.hpp +++ b/libraries/app/include/graphene/app/api.hpp @@ -322,6 +322,7 @@ namespace graphene { namespace app { vector get_vested_balances( const vector& objs )const; + vector get_vesting_balances( account_id_type account_id )const; /** * This API will take a partially signed transaction and a set of public keys that the owner has the ability to sign for @@ -579,6 +580,7 @@ FC_API(graphene::app::database_api, (get_margin_positions) (get_balance_objects) (get_vested_balances) + (get_vesting_balances) (get_required_signatures) (get_potential_signatures) (verify_authority) diff --git a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp index 3386d021..ee14dc89 100644 --- a/libraries/chain/include/graphene/chain/vesting_balance_object.hpp +++ b/libraries/chain/include/graphene/chain/vesting_balance_object.hpp @@ -155,6 +155,11 @@ namespace graphene { namespace chain { */ void withdraw(const fc::time_point_sec& now, const asset& amount); bool is_withdraw_allowed(const fc::time_point_sec& now, const asset& amount)const; + + /** + * Get amount of allowed withdrawal. + */ + asset get_allowed_withdraw(const time_point_sec& now)const; }; /** * @ingroup object_index diff --git a/libraries/chain/vesting_balance_object.cpp b/libraries/chain/vesting_balance_object.cpp index d4f70d75..799863f1 100644 --- a/libraries/chain/vesting_balance_object.cpp +++ b/libraries/chain/vesting_balance_object.cpp @@ -182,6 +182,7 @@ VESTING_VISITOR(on_withdraw,); VESTING_VISITOR(is_deposit_allowed, const); VESTING_VISITOR(is_deposit_vested_allowed, const); VESTING_VISITOR(is_withdraw_allowed, const); +VESTING_VISITOR(get_allowed_withdraw, const); bool vesting_balance_object::is_deposit_allowed(const time_point_sec& now, const asset& amount)const { @@ -224,4 +225,10 @@ void vesting_balance_object::withdraw(const time_point_sec& now, const asset& am balance -= amount; } +asset vesting_balance_object::get_allowed_withdraw(const time_point_sec& now)const +{ + asset amount = asset(); + return policy.visit(get_allowed_withdraw_visitor(balance, now, amount)); +} + } } // graphene::chain diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index c638da87..24eb0309 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -116,49 +116,8 @@ void witness_plugin::plugin_startup() { try { ilog("witness plugin: plugin_startup() begin"); chain::database& d = database(); - std::set bad_wits; //Start NTP time client graphene::time::now(); - for( auto wit : _witnesses ) - { - if( d.find(wit) == nullptr ) - { - if( app().is_finished_syncing() ) - { - elog("ERROR: Unable to find witness ${w}, even though syncing has finished. This witness will be ignored.", - ("w", wit)); - continue; - } else { - wlog("WARNING: Unable to find witness ${w}. Postponing initialization until syncing finishes.", - ("w", wit)); - app().syncing_finished.connect([this]{plugin_startup();}); - return; - } - } - - auto signing_key = wit(d).signing_key; - if( !_private_keys.count(signing_key) ) - { - // Check if it's a duplicate key of one I do have - bool found_duplicate = false; - for( const auto& private_key : _private_keys ) - if( chain::public_key_type(private_key.second.get_public_key()) == signing_key ) - { - ilog("Found duplicate key: ${k1} matches ${k2}; using this key to sign for ${w}", - ("k1", private_key.first)("k2", signing_key)("w", wit)); - _private_keys[signing_key] = private_key.second; - found_duplicate = true; - break; - } - if( found_duplicate ) - continue; - - elog("Unable to find key for witness ${w}. Removing it from my witnesses.", ("w", wit)); - bad_wits.insert(wit); - } - } - for( auto wit : bad_wits ) - _witnesses.erase(wit); if( !_witnesses.empty() ) { @@ -211,7 +170,7 @@ block_production_condition::block_production_condition_enum witness_plugin::bloc switch( result ) { case block_production_condition::produced: - ilog("Generated block #${n} with timestamp ${t} at time ${c}", ("n",capture)("t",capture)("c",capture)); + ilog("Generated block #${n} with timestamp ${t} at time ${c}", (capture)); break; case block_production_condition::not_synced: ilog("Not producing block because production is disabled until we receive a recent block (see: --enable-stale-production)"); @@ -223,10 +182,10 @@ block_production_condition::block_production_condition_enum witness_plugin::bloc ilog("Not producing block because slot has not yet arrived"); break; case block_production_condition::no_private_key: - ilog("Not producing block because I don't have the private key for ${scheduled_key}", ("scheduled_key",capture) ); + ilog("Not producing block because I don't have the private key for ${scheduled_key}", (capture) ); break; case block_production_condition::low_participation: - elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", ("pct",capture) ); + elog("Not producing block because node appears to be on a minority fork with only ${pct}% witness participation", (capture) ); break; case block_production_condition::lag: elog("Not producing block because node didn't wake up within 500ms of the slot time."); diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 3ebfe0f1..590212fe 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -219,7 +219,23 @@ struct signed_block_with_info : public signed_block signed_block_with_info( const signed_block_with_info& block ) = default; block_id_type block_id; - fc::ecc::public_key signing_key; + public_key_type signing_key; +}; + +struct vesting_balance_object_with_info : public vesting_balance_object +{ + vesting_balance_object_with_info( const vesting_balance_object& vbo, fc::time_point_sec now ); + vesting_balance_object_with_info( const vesting_balance_object_with_info& vbo ) = default; + + /** + * How much is allowed to be withdrawn. + */ + asset allowed_withdraw; + + /** + * The time at which allowed_withdrawal was calculated. + */ + fc::time_point_sec allowed_withdraw_time; }; namespace detail { @@ -1086,6 +1102,40 @@ class wallet_api string url, bool broadcast = false); + /** + * Update a witness object owned by the given account. + * + * @param witness The name of the witness's owner account. Also accepts the ID of the owner account or the ID of the witness. + * @param url Same as for create_witness. The empty string makes it remain the same. + * @param block_signing_key The new block signing public key. The empty string makes it remain the same. + * @param broadcast true if you wish to broadcast the transaction. + */ + signed_transaction update_witness(string witness_name, + string url, + string block_signing_key, + bool broadcast = false); + + /** + * Get information about a vesting balance object. + * + * @param account_name An account name, account ID, or vesting balance object ID. + */ + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ); + + /** + * Withdraw a vesting balance. + * + * @param witness_name The account name of the witness, also accepts account ID or vesting balance ID type. + * @param amount The amount to withdraw. + * @param asset_symbol The symbol of the asset to withdraw. + * @param broadcast true if you wish to broadcast the transaction + */ + signed_transaction withdraw_vesting( + string witness_name, + string amount, + string asset_symbol, + bool broadcast = false); + /** Vote for a given committee_member. * * An account can publish a list of all committee_memberes they approve of. This @@ -1314,6 +1364,9 @@ FC_REFLECT( graphene::wallet::approval_delta, FC_REFLECT_DERIVED( graphene::wallet::signed_block_with_info, (graphene::chain::signed_block), (block_id)(signing_key) ) +FC_REFLECT_DERIVED( graphene::wallet::vesting_balance_object_with_info, (graphene::chain::vesting_balance_object), + (allowed_withdraw)(allowed_withdraw_time) ) + FC_API( graphene::wallet::wallet_api, (help) (gethelp) @@ -1364,6 +1417,9 @@ FC_API( graphene::wallet::wallet_api, (list_witnesses) (list_committee_members) (create_witness) + (update_witness) + (get_vesting_balances) + (withdraw_vesting) (vote_for_committee_member) (vote_for_witness) (set_voting_proxy) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index c0231841..10c4a72a 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -1318,6 +1318,90 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + signed_transaction update_witness(string witness_name, + string url, + string block_signing_key, + bool broadcast /* = false */) + { try { + witness_object witness = get_witness(witness_name); + account_object witness_account = get_account( witness.witness_account ); + fc::ecc::private_key active_private_key = get_private_key_for_account(witness_account); + + witness_update_operation witness_update_op; + witness_update_op.witness = witness.id; + witness_update_op.witness_account = witness_account.id; + if( url != "" ) + witness_update_op.new_url = url; + if( block_signing_key != "" ) + witness_update_op.new_signing_key = public_key_type( block_signing_key ); + + signed_transaction tx; + tx.operations.push_back( witness_update_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (witness_name)(url)(block_signing_key)(broadcast) ) } + + vector< vesting_balance_object_with_info > get_vesting_balances( string account_name ) + { try { + fc::optional vbid = maybe_id( account_name ); + std::vector result; + fc::time_point_sec now = _remote_db->get_dynamic_global_properties().time; + + if( vbid ) + { + result.emplace_back( get_object(*vbid), now ); + return result; + } + + // try casting to avoid a round-trip if we were given an account ID + fc::optional acct_id = maybe_id( account_name ); + if( !acct_id ) + acct_id = get_account( account_name ).id; + + vector< vesting_balance_object > vbos = _remote_db->get_vesting_balances( *acct_id ); + if( vbos.size() == 0 ) + return result; + + for( const vesting_balance_object& vbo : vbos ) + result.emplace_back( vbo, now ); + + return result; + } FC_CAPTURE_AND_RETHROW( (account_name) ) + } + + signed_transaction withdraw_vesting( + string witness_name, + string amount, + string asset_symbol, + bool broadcast = false ) + { try { + asset_object asset_obj = get_asset( asset_symbol ); + fc::optional vbid = maybe_id(witness_name); + if( !vbid ) + { + witness_object wit = get_witness( witness_name ); + FC_ASSERT( wit.pay_vb ); + vbid = wit.pay_vb; + } + + vesting_balance_object vbo = get_object< vesting_balance_object >( *vbid ); + vesting_balance_withdraw_operation vesting_balance_withdraw_op; + + vesting_balance_withdraw_op.vesting_balance = *vbid; + vesting_balance_withdraw_op.owner = vbo.owner; + vesting_balance_withdraw_op.amount = asset_obj.amount_from_string(amount); + + signed_transaction tx; + tx.operations.push_back( vesting_balance_withdraw_op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (witness_name)(amount) ) + } + signed_transaction vote_for_committee_member(string voting_account, string committee_member, bool approve, @@ -2546,6 +2630,29 @@ signed_transaction wallet_api::create_witness(string owner_account, return my->create_witness(owner_account, url, broadcast); } +signed_transaction wallet_api::update_witness( + string witness_name, + string url, + string block_signing_key, + bool broadcast /* = false */) +{ + return my->update_witness(witness_name, url, block_signing_key, broadcast); +} + +vector< vesting_balance_object_with_info > wallet_api::get_vesting_balances( string account_name ) +{ + return my->get_vesting_balances( account_name ); +} + +signed_transaction wallet_api::withdraw_vesting( + string witness_name, + string amount, + string asset_symbol, + bool broadcast /* = false */) +{ + return my->withdraw_vesting( witness_name, amount, asset_symbol, broadcast ); +} + signed_transaction wallet_api::vote_for_committee_member(string voting_account, string witness, bool approve, @@ -3396,6 +3503,13 @@ signed_block_with_info::signed_block_with_info( const signed_block& block ) signing_key = signee(); } +vesting_balance_object_with_info::vesting_balance_object_with_info( const vesting_balance_object& vbo, fc::time_point_sec now ) + : vesting_balance_object( vbo ) +{ + allowed_withdraw = get_allowed_withdraw( now ); + allowed_withdraw_time = now; +} + } } // graphene::wallet void fc::to_variant(const account_multi_index_type& accts, fc::variant& vo)