diff --git a/.clang-format b/.clang-format new file mode 100755 index 00000000..2fffe7ba --- /dev/null +++ b/.clang-format @@ -0,0 +1,127 @@ +--- +Language: Cpp +# BasedOnStyle: LLVM +AccessModifierOffset: -3 +AlignAfterOpenBracket: Align +AlignConsecutiveMacros: false +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Right +AlignOperands: true +AlignTrailingComments: true +AllowAllArgumentsOnNextLine: true +AllowAllConstructorInitializersOnNextLine: false +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: None +AllowShortLambdasOnASingleLine: None +AllowShortIfStatementsOnASingleLine: Never +AllowShortLoopsOnASingleLine: false +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: false +AlwaysBreakTemplateDeclarations: MultiLine +BinPackArguments: true +BinPackParameters: true +BraceWrapping: + AfterCaseLabel: false + AfterClass: false + AfterControlStatement: false + AfterEnum: false + AfterFunction: false + AfterNamespace: false + AfterObjCDeclaration: false + AfterStruct: false + AfterUnion: false + AfterExternBlock: false + BeforeCatch: false + BeforeElse: false + IndentBraces: false + SplitEmptyFunction: true + SplitEmptyRecord: true + SplitEmptyNamespace: true +BreakBeforeBinaryOperators: None +BreakBeforeBraces: Attach +BreakBeforeInheritanceComma: false +BreakInheritanceList: BeforeColon +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakConstructorInitializers: AfterColon +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: false +ColumnLimit: 0 +CommentPragmas: '^ IWYU pragma:' +CompactNamespaces: true +ConstructorInitializerAllOnOneLineOrOnePerLine: true +ConstructorInitializerIndentWidth: 6 +ContinuationIndentWidth: 6 +Cpp11BracedListStyle: true +DerivePointerAlignment: false +DisableFormat: false +ExperimentalAutoDetectBinPacking: false +FixNamespaceComments: true +ForEachMacros: + - foreach + - Q_FOREACH + - BOOST_FOREACH +IncludeBlocks: Preserve +IncludeCategories: + - Regex: '^"(llvm|llvm-c|clang|clang-c)/' + Priority: 2 + - Regex: '^(<|"(gtest|gmock|isl|json)/)' + Priority: 3 + - Regex: '.*' + Priority: 1 +IncludeIsMainRegex: '(Test)?$' +IndentCaseLabels: false +IndentPPDirectives: None +IndentWidth: 3 +IndentWrappedFunctionNames: false +JavaScriptQuotes: Leave +JavaScriptWrapImports: true +KeepEmptyLinesAtTheStartOfBlocks: true +MacroBlockBegin: '' +MacroBlockEnd: '' +MaxEmptyLinesToKeep: 1 +NamespaceIndentation: None +ObjCBinPackProtocolList: Auto +ObjCBlockIndentWidth: 2 +ObjCSpaceAfterProperty: false +ObjCSpaceBeforeProtocolList: true +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 19 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyBreakTemplateDeclaration: 10 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 60 +PointerAlignment: Right +ReflowComments: true +SortIncludes: true +SortUsingDeclarations: true +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 1 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false +Standard: Cpp11 +StatementMacros: + - Q_UNUSED + - QT_REQUIRE_VERSION +TabWidth: 3 +UseTab: Never +... + diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 188f4a45..530caf2f 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -8,6 +8,7 @@ stages: build: stage: build script: + - git submodule sync - git submodule update --init --recursive - cmake . - make -j$(nproc) diff --git a/CMakeLists.txt b/CMakeLists.txt index 6853e2c8..b26bbc57 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -135,7 +135,7 @@ else( WIN32 ) # Apple AND Linux endif( APPLE ) if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) - set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-parentheses -Wno-invalid-offsetof" ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp -Wno-parentheses -Wno-invalid-offsetof -Wno-terminate -Wno-sign-compare" ) elseif( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" ) if( CMAKE_CXX_COMPILER_VERSION VERSION_EQUAL 4.0.0 OR CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 4.0.0 ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wno-invalid-partial-specialization" ) diff --git a/Dockerfile b/Dockerfile index 8a970e39..dc4caae4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -22,6 +22,7 @@ RUN \ libreadline-dev \ libssl-dev \ libtool \ + libzmq3-dev \ locales \ ntp \ pkg-config \ diff --git a/README.md b/README.md index 8207bb29..941afa68 100644 --- a/README.md +++ b/README.md @@ -7,9 +7,10 @@ This is a quick introduction to get new developers and witnesses up to speed on The following dependencies were necessary for a clean install of Ubuntu 18.04 LTS: ``` - sudo apt-get install gcc-5 g++-5 cmake make libbz2-dev\ - libdb++-dev libdb-dev libssl-dev openssl libreadline-dev\ - autoconf libtool git + sudo apt-get install autoconf bash build-essential ca-certificates cmake \ + doxygen git graphviz libbz2-dev libcurl4-openssl-dev libncurses-dev \ + libreadline-dev libssl-dev libtool libzmq3-dev locales ntp pkg-config \ + wget ``` ## Build Boost 1.67.0 diff --git a/libraries/app/CMakeLists.txt b/libraries/app/CMakeLists.txt index 93e540f9..e6f8940c 100644 --- a/libraries/app/CMakeLists.txt +++ b/libraries/app/CMakeLists.txt @@ -14,7 +14,7 @@ add_library( graphene_app # need to link graphene_debug_witness because plugins aren't sufficiently isolated #246 #target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_chain fc graphene_db graphene_net graphene_utilities graphene_debug_witness ) -target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_affiliate_stats graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie ) +target_link_libraries( graphene_app graphene_market_history graphene_account_history graphene_accounts_list graphene_affiliate_stats graphene_chain fc graphene_db graphene_net graphene_time graphene_utilities graphene_debug_witness graphene_bookie peerplays_sidechain ) target_include_directories( graphene_app PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_SOURCE_DIR}/../egenesis/include" ) diff --git a/libraries/app/api.cpp b/libraries/app/api.cpp index 833069f8..d31abe19 100644 --- a/libraries/app/api.cpp +++ b/libraries/app/api.cpp @@ -443,6 +443,11 @@ namespace graphene { namespace app { assert( aobj != nullptr ); accounts.insert( aobj->son_account ); break; + } case sidechain_address_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->sidechain_address_account ); + break; } case sport_object_type: case event_group_object_type: diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index b9ae31b6..c6c8a952 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -103,7 +103,7 @@ class database_api_impl : public std::enable_shared_from_this uint64_t get_asset_count()const; // Peerplays - vector list_sports() const; + vector list_sports() const; vector list_event_groups(sport_id_type sport_id) const; vector list_events_in_group(event_group_id_type event_group_id) const; vector list_betting_market_groups(event_id_type) const; @@ -115,14 +115,14 @@ class database_api_impl : public std::enable_shared_from_this vector get_lotteries( asset_id_type stop = asset_id_type(), unsigned limit = 100, asset_id_type start = asset_id_type() )const; - vector get_account_lotteries( account_id_type issuer, + vector get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const; asset get_lottery_balance( asset_id_type lottery_id )const; sweeps_vesting_balance_object get_sweeps_vesting_balance_object( account_id_type account )const; asset get_sweeps_vesting_balance_available_for_claim( account_id_type account )const; - + // Markets / feeds vector get_limit_orders(asset_id_type a, asset_id_type b, uint32_t limit)const; vector get_call_orders(asset_id_type a, uint32_t limit)const; @@ -152,6 +152,18 @@ class database_api_impl : public std::enable_shared_from_this map lookup_son_accounts(const string& lower_bound_name, uint32_t limit)const; uint64_t get_son_count()const; + // SON wallets + optional get_active_son_wallet(); + optional get_son_wallet_by_time_point(time_point_sec time_point); + vector> get_son_wallets(uint32_t limit); + + // Sidechain addresses + vector> get_sidechain_addresses(const vector& sidechain_address_ids)const; + vector> get_sidechain_addresses_by_account(account_id_type account)const; + vector> get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain)const; + fc::optional get_sidechain_address_by_account_and_sidechain(account_id_type account, peerplays_sidechain::sidechain_type sidechain)const; + uint64_t get_sidechain_addresses_count()const; + // Votes vector lookup_vote_ids( const vector& votes )const; @@ -528,11 +540,11 @@ vector> database_api::get_key_references( vector> database_api_impl::get_key_references( vector keys )const { wdump( (keys) ); - + const auto& idx = _db.get_index_type(); const auto& aidx = dynamic_cast(idx); const auto& refs = aidx.get_secondary_index(); - + vector< vector > final_result; final_result.reserve(keys.size()); @@ -648,7 +660,7 @@ std::map database_api_impl::get_full_accounts( const const auto& proposal_idx = _db.get_index_type(); const auto& pidx = dynamic_cast(proposal_idx); const auto& proposals_by_account = pidx.get_secondary_index(); - + std::map results; for (const std::string& account_name_or_id : names_or_ids) @@ -738,7 +750,7 @@ std::map database_api_impl::get_full_accounts( const acnt.withdraws.emplace_back(withdraw); }); - auto pending_payouts_range = + auto pending_payouts_range = _db.get_index_type().indices().get().equal_range(boost::make_tuple(account->id)); std::copy(pending_payouts_range.first, pending_payouts_range.second, std::back_inserter(acnt.pending_dividend_payments)); @@ -1058,7 +1070,7 @@ vector database_api_impl::get_lotteries( asset_id_type stop, return result; } -vector database_api::get_account_lotteries( account_id_type issuer, +vector database_api::get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const @@ -1066,7 +1078,7 @@ vector database_api::get_account_lotteries( account_id_type issuer return my->get_account_lotteries( issuer, stop, limit, start ); } -vector database_api_impl::get_account_lotteries( account_id_type issuer, +vector database_api_impl::get_account_lotteries( account_id_type issuer, asset_id_type stop, unsigned limit, asset_id_type start )const @@ -1763,6 +1775,136 @@ uint64_t database_api_impl::get_son_count()const return _db.get_index_type().indices().size(); } +////////////////////////////////////////////////////////////////////// +// // +// SON Wallets // +// // +////////////////////////////////////////////////////////////////////// + +optional database_api::get_active_son_wallet() +{ + return my->get_active_son_wallet(); +} + +optional database_api_impl::get_active_son_wallet() +{ + const auto& idx = _db.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj != idx.rend()) { + return *obj; + } + return {}; +} + +optional database_api::get_son_wallet_by_time_point(time_point_sec time_point) +{ + return my->get_son_wallet_by_time_point(time_point); +} + +optional database_api_impl::get_son_wallet_by_time_point(time_point_sec time_point) +{ + const auto& son_wallets_by_id = _db.get_index_type().indices().get(); + for (const son_wallet_object& swo : son_wallets_by_id) { + if ((time_point >= swo.valid_from) && (time_point < swo.expires)) + return swo; + } + return {}; +} + +vector> database_api::get_son_wallets(uint32_t limit) +{ + return my->get_son_wallets(limit); +} + +vector> database_api_impl::get_son_wallets(uint32_t limit) +{ + FC_ASSERT( limit <= 1000 ); + vector> result; + const auto& son_wallets_by_id = _db.get_index_type().indices().get(); + for (const son_wallet_object& swo : son_wallets_by_id) + result.push_back(swo); + return result; +} + +////////////////////////////////////////////////////////////////////// +// // +// Sidechain Accounts // +// // +////////////////////////////////////////////////////////////////////// + +vector> database_api::get_sidechain_addresses(const vector& sidechain_address_ids)const +{ + return my->get_sidechain_addresses( sidechain_address_ids ); +} + +vector> database_api_impl::get_sidechain_addresses(const vector& sidechain_address_ids)const +{ + vector> result; result.reserve(sidechain_address_ids.size()); + std::transform(sidechain_address_ids.begin(), sidechain_address_ids.end(), std::back_inserter(result), + [this](sidechain_address_id_type id) -> optional { + if(auto o = _db.find(id)) + return *o; + return {}; + }); + return result; +} + +vector> database_api::get_sidechain_addresses_by_account(account_id_type account)const +{ + return my->get_sidechain_addresses_by_account( account ); +} + +vector> database_api_impl::get_sidechain_addresses_by_account(account_id_type account)const +{ + vector> result; + const auto& sidechain_addresses_range = _db.get_index_type().indices().get().equal_range(account); + std::for_each(sidechain_addresses_range.first, sidechain_addresses_range.second, + [&result] (const sidechain_address_object& sao) { + result.push_back(sao); + }); + return result; +} + +vector> database_api::get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain)const +{ + return my->get_sidechain_addresses_by_sidechain( sidechain ); +} + +vector> database_api_impl::get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain)const +{ + vector> result; + const auto& sidechain_addresses_range = _db.get_index_type().indices().get().equal_range(sidechain); + std::for_each(sidechain_addresses_range.first, sidechain_addresses_range.second, + [&result] (const sidechain_address_object& sao) { + result.push_back(sao); + }); + return result; +} + +fc::optional database_api::get_sidechain_address_by_account_and_sidechain(account_id_type account, peerplays_sidechain::sidechain_type sidechain)const +{ + return my->get_sidechain_address_by_account_and_sidechain( account, sidechain ); +} + +fc::optional database_api_impl::get_sidechain_address_by_account_and_sidechain(account_id_type account, peerplays_sidechain::sidechain_type sidechain)const +{ + const auto& idx = _db.get_index_type().indices().get(); + auto itr = idx.find( boost::make_tuple( account, sidechain ) ); + if( itr != idx.end() ) + return *itr; + return {}; +} + +uint64_t database_api::get_sidechain_addresses_count()const +{ + return my->get_sidechain_addresses_count(); +} + +uint64_t database_api_impl::get_sidechain_addresses_count()const +{ + return _db.get_index_type().indices().size(); +} + ////////////////////////////////////////////////////////////////////// // // // Votes // @@ -2164,7 +2306,7 @@ vector database_api::get_tournaments(tournament_id_type stop, vector database_api_impl::get_tournaments(tournament_id_type stop, unsigned limit, - tournament_id_type start) + tournament_id_type start) { vector result; const auto& tournament_idx = _db.get_index_type().indices().get(); @@ -2191,7 +2333,7 @@ vector database_api_impl::get_tournaments_by_state(tournament unsigned limit, tournament_id_type start, tournament_state state) -{ +{ vector result; const auto& tournament_idx = _db.get_index_type().indices().get(); for (auto elem: tournament_idx) { @@ -2320,7 +2462,7 @@ void database_api_impl::handle_object_changed(bool force_notify, bool full_objec /// if a connection hangs then this could get backed up and result in /// a failure to exit cleanly. //fc::async([capture_this,this,updates,market_broadcast_queue](){ - //if( _subscribe_callback ) + //if( _subscribe_callback ) // _subscribe_callback( updates ); for(auto id : ids) diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 997a3a38..e95817aa 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -307,6 +307,51 @@ struct get_impacted_account_visitor void operator()( const son_delete_operation& op ){ _impacted.insert( op.owner_account ); } + void operator()( const son_heartbeat_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const son_report_down_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_maintenance_operation& op ){ + _impacted.insert( op.owner_account ); + } + void operator()( const son_wallet_recreate_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_update_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_create_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_process_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_create_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_process_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const sidechain_address_add_operation& op ){ + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_address_update_operation& op ){ + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_address_delete_operation& op ){ + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_transaction_create_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_sign_operation& op ){ + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_send_operation& op ){ + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/app/include/graphene/app/database_api.hpp b/libraries/app/include/graphene/app/database_api.hpp index d597110e..76ef822c 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -44,6 +44,8 @@ #include #include #include +#include +#include #include #include @@ -602,6 +604,70 @@ class database_api */ uint64_t get_son_count()const; + ///////////////////////// + // SON Wallets // + ///////////////////////// + + /** + * @brief Get active SON wallet + * @return Active SON wallet object + */ + optional get_active_son_wallet(); + + /** + * @brief Get SON wallet that was active for a given time point + * @param time_point Time point + * @return SON wallet object, for the wallet that was active for a given time point + */ + optional get_son_wallet_by_time_point(time_point_sec time_point); + + /** + * @brief Get full list of SON wallets + * @param limit Maximum number of results to return + * @return A list of SON wallet objects + */ + vector> get_son_wallets(uint32_t limit); + + ///////////////////////// + // Sidechain Addresses // + ///////////////////////// + + /** + * @brief Get a list of sidechain addresses + * @param sidechain_address_ids IDs of the sidechain addresses to retrieve + * @return The sidechain accounts corresponding to the provided IDs + * + * This function has semantics identical to @ref get_objects + */ + vector> get_sidechain_addresses(const vector& sidechain_address_ids)const; + + /** + * @brief Get the sidechain addresses for a given account + * @param account The ID of the account whose sidechain addresses should be retrieved + * @return The sidechain addresses objects, or null if the account does not have a sidechain addresses + */ + vector> get_sidechain_addresses_by_account(account_id_type account)const; + + /** + * @brief Get the sidechain addresses for a given sidechain + * @param sidechain Sidechain for which addresses should be retrieved + * @return The sidechain addresses objects, or null if the sidechain does not have any addresses + */ + vector> get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain)const; + + /** + * @brief Get the sidechain addresses for a given account and sidechain + * @param account The ID of the account whose sidechain addresses should be retrieved + * @param sidechain Sidechain for which address should be retrieved + * @return The sidechain addresses objects, or null if the account does not have a sidechain addresses for a given sidechain + */ + fc::optional get_sidechain_address_by_account_and_sidechain(account_id_type account, peerplays_sidechain::sidechain_type sidechain)const; + + /** + * @brief Get the total number of sidechain addresses registered with the blockchain + */ + uint64_t get_sidechain_addresses_count()const; + /// WORKERS /** @@ -814,6 +880,18 @@ FC_API(graphene::app::database_api, (lookup_son_accounts) (get_son_count) + // SON wallets + (get_active_son_wallet) + (get_son_wallet_by_time_point) + (get_son_wallets) + + // Sidechain addresses + (get_sidechain_addresses) + (get_sidechain_addresses_by_account) + (get_sidechain_addresses_by_sidechain) + (get_sidechain_address_by_account_and_sidechain) + (get_sidechain_addresses_count) + // workers (get_workers_by_account) // Votes diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt old mode 100644 new mode 100755 index 5688eabf..73db113d --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -19,6 +19,7 @@ if( GRAPHENE_DISABLE_UNITY_BUILD ) db_maint.cpp db_management.cpp db_market.cpp + db_sidechain.cpp db_update.cpp db_witness_schedule.cpp ) @@ -116,6 +117,13 @@ add_library( graphene_chain son_evaluator.cpp son_object.cpp + son_wallet_evaluator.cpp + son_wallet_deposit_evaluator.cpp + son_wallet_withdraw_evaluator.cpp + + sidechain_address_evaluator.cpp + sidechain_transaction_evaluator.cpp + ${HEADERS} ${PROTOCOL_HEADERS} "${CMAKE_CURRENT_BINARY_DIR}/include/graphene/chain/hardfork.hpp" diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 7711f543..36e2a161 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -31,6 +31,7 @@ #include "db_maint.cpp" #include "db_management.cpp" #include "db_market.cpp" +#include "db_sidechain.cpp" #include "db_update.cpp" #include "db_witness_schedule.cpp" #include "db_notify.cpp" \ No newline at end of file diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index dafdc3ff..dfa6c4d1 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -336,8 +336,6 @@ processed_transaction database::validate_transaction( const signed_transaction& processed_transaction database::push_proposal(const proposal_object& proposal) { try { - FC_ASSERT( _undo_db.size() < _undo_db.max_size(), "Undo database is full!" ); - transaction_evaluation_state eval_state(this); eval_state._is_proposed_trx = true; @@ -347,6 +345,8 @@ processed_transaction database::push_proposal(const proposal_object& proposal) size_t old_applied_ops_size = _applied_ops.size(); try { + if( _undo_db.size() >= _undo_db.max_size() ) + _undo_db.set_max_size( _undo_db.size() + 1 ); auto session = _undo_db.start_undo_session(true); for( auto& op : proposal.proposed_transaction.operations ) eval_state.operation_results.emplace_back(apply_operation(eval_state, op)); @@ -427,34 +427,6 @@ signed_block database::_generate_block( _pending_tx_session.reset(); _pending_tx_session = _undo_db.start_undo_session(); - if( head_block_time() > HARDFORK_SON_TIME ) - { - // Approve proposals raised by me in previous schedule or before - process_son_proposals( witness_obj, block_signing_private_key ); - // Check for new SON Deregistration Proposals to be raised - std::set sons_to_be_dereg = get_sons_to_be_deregistered(); - if(sons_to_be_dereg.size() > 0) - { - // We shouldn't raise proposals for the SONs for which a de-reg - // proposal is already raised. - std::set sons_being_dereg = get_sons_being_deregistered(); - for( auto& son : sons_to_be_dereg) - { - // New SON to be deregistered - if(sons_being_dereg.find(son) == sons_being_dereg.end()) - { - // Creating the de-reg proposal - auto op = create_son_deregister_proposal(son, witness_obj); - if(op.valid()) - { - // Signing and pushing into the txs to be included in the block - _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( block_signing_private_key, *op ) ); - } - } - } - } - } - uint64_t postponed_tx_count = 0; // pop pending state (reset to head block state) for( const processed_transaction& tx : _pending_tx ) @@ -649,8 +621,13 @@ void database::_apply_block( const signed_block& next_block ) _current_virtual_op = 0; } - if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) - update_witness_schedule(next_block); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM) { + update_witness_schedule(next_block); + if(global_props.active_sons.size() > 0) { + update_son_schedule(next_block); + } + } + const uint32_t missed = update_witness_missed_blocks( next_block ); update_global_dynamic_data( next_block, missed ); update_signing_witness(signing_witness, next_block); @@ -678,8 +655,13 @@ void database::_apply_block( const signed_block& next_block ) // update_global_dynamic_data() as perhaps these methods only need // to be called for header validation? update_maintenance_flag( maint_needed ); - if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) - update_witness_schedule(); + if (global_props.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) { + update_witness_schedule(); + if(global_props.active_sons.size() > 0) { + update_son_schedule(); + } + } + if( !_node_property_object.debug_updates.empty() ) apply_debug_updates(); diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index a23ff6de..9e8bfa80 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -170,7 +170,7 @@ std::set database::get_sons_to_be_deregistered() // TODO : We need to add a function that returns if we can deregister SON // i.e. with introduction of PW code, we have to make a decision if the SON // is needed for release of funds from the PW - if(head_block_time() - stats.last_down_timestamp >= fc::hours(SON_DEREGISTER_TIME)) + if(head_block_time() - stats.last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time())) { ret.insert(son.id); } @@ -179,14 +179,29 @@ std::set database::get_sons_to_be_deregistered() return ret; } -fc::optional database::create_son_deregister_proposal(const son_id_type& son_id, const witness_object& current_witness ) +std::set database::get_sons_being_reported_down() +{ + std::set ret; + const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); + + for( auto& son_proposal : son_proposal_idx ) + { + if(son_proposal.proposal_type == son_proposal_type::son_report_down_proposal) + { + ret.insert(son_proposal.son_id); + } + } + return ret; +} + +fc::optional database::create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ) { son_delete_operation son_dereg_op; - son_dereg_op.payer = current_witness.witness_account; + son_dereg_op.payer = get_global_properties().parameters.son_account(); son_dereg_op.son_id = son_id; proposal_create_operation proposal_op; - proposal_op.fee_paying_account = current_witness.witness_account; + proposal_op.fee_paying_account = paying_son; proposal_op.proposed_ops.push_back( op_wrapper( son_dereg_op ) ); uint32_t lifetime = ( get_global_properties().parameters.block_interval * get_global_properties().active_witnesses.size() ) * 3; proposal_op.expiration_time = time_point_sec( head_block_time().sec_since_epoch() + lifetime ); @@ -207,52 +222,13 @@ signed_transaction database::create_signed_transaction( const fc::ecc::private_k return processed_trx; } -void database::process_son_proposals( const witness_object& current_witness, const fc::ecc::private_key& private_key ) -{ - const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); - const auto& proposal_idx = get_index_type().indices().get< by_id >(); - - auto approve_proposal = [ & ]( const proposal_id_type& id ) - { - proposal_update_operation puo; - puo.fee_paying_account = current_witness.witness_account; - puo.proposal = id; - puo.active_approvals_to_add = { current_witness.witness_account }; - _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( private_key, puo ) ); - }; - - for( auto& son_proposal : son_proposal_idx ) - { - const auto& proposal = proposal_idx.find( son_proposal.proposal_id ); - FC_ASSERT( proposal != proposal_idx.end() ); - if( proposal->proposer == current_witness.witness_account) - { - approve_proposal( proposal->id ); - } - } -} - -void database::remove_son_proposal( const proposal_object& proposal ) -{ try { - if( proposal.proposed_transaction.operations.size() == 1 && - ( proposal.proposed_transaction.operations.back().which() == operation::tag::value) ) - { - const auto& son_proposal_idx = get_index_type().indices().get(); - auto son_proposal_itr = son_proposal_idx.find( proposal.id ); - if( son_proposal_itr == son_proposal_idx.end() ) { - return; - } - remove( *son_proposal_itr ); - } -} FC_CAPTURE_AND_RETHROW( (proposal) ) } - -bool database::is_son_dereg_valid( const son_id_type& son_id ) +bool database::is_son_dereg_valid( son_id_type son_id ) { const auto& son_idx = get_index_type().indices().get< by_id >(); auto son = son_idx.find( son_id ); FC_ASSERT( son != son_idx.end() ); bool ret = ( son->status == son_status::in_maintenance && - (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::hours(SON_DEREGISTER_TIME))); + (head_block_time() - son->statistics(*this).last_down_timestamp >= fc::seconds(get_global_properties().parameters.son_deregister_time()))); return ret; } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index 7dc986a4..b3311c2d 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -56,6 +56,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include @@ -78,6 +83,11 @@ #include #include #include +#include +#include +#include +#include +#include #include @@ -247,6 +257,21 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -263,7 +288,7 @@ void database::initialize_indexes() acnt_index->add_secondary_index(); add_index< primary_index >(); // 256 members per chunk - add_index< primary_index >(); // 256 sons per chunk + add_index< primary_index >(); add_index< primary_index >(); // 1024 witnesses per chunk add_index< primary_index >(); add_index< primary_index >(); @@ -291,6 +316,13 @@ void database::initialize_indexes() add_index< primary_index >(); add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + add_index< primary_index >(); + + add_index< primary_index >(); + add_index< primary_index >(); + //Implementation object indexes add_index< primary_index >(); @@ -306,6 +338,7 @@ void database::initialize_indexes() add_index< primary_index> >(); add_index< primary_index > >(); add_index< primary_index > >(); + add_index< primary_index > >(); add_index< primary_index > >(); add_index< primary_index< special_authority_index > >(); add_index< primary_index< buyback_index > >(); @@ -939,6 +972,29 @@ void database::init_genesis(const genesis_state_type& genesis_state) }); assert( wso.id == witness_schedule_id_type() ); + // Initialize witness schedule +#ifndef NDEBUG + const son_schedule_object& sso = +#endif + create([&](son_schedule_object& _sso) + { + // for scheduled + memset(_sso.rng_seed.begin(), 0, _sso.rng_seed.size()); + + witness_scheduler_rng rng(_sso.rng_seed.begin(), GRAPHENE_NEAR_SCHEDULE_CTR_IV); + + auto init_witnesses = get_global_properties().active_witnesses; + + _sso.scheduler = son_scheduler(); + _sso.scheduler._min_token_count = std::max(int(init_witnesses.size()) / 2, 1); + + + _sso.last_scheduling_block = 0; + + _sso.recent_slots_filled = fc::uint128::max_value(); + }); + assert( sso.id == son_schedule_id_type() ); + // Enable fees modify(get_global_properties(), [&genesis_state](global_property_object& p) { p.parameters.current_fees = genesis_state.initial_parameters.current_fees; diff --git a/libraries/chain/db_maint.cpp b/libraries/chain/db_maint.cpp index cae17eda..498a61ac 100644 --- a/libraries/chain/db_maint.cpp +++ b/libraries/chain/db_maint.cpp @@ -42,6 +42,7 @@ #include #include #include +#include #include #include #include @@ -122,7 +123,7 @@ void database::pay_sons() time_point_sec now = head_block_time(); const dynamic_global_property_object& dpo = get_dynamic_global_properties(); // Current requirement is that we have to pay every 24 hours, so the following check - if( dpo.son_budget.value > 0 && now - dpo.last_son_payout_time >= fc::days(1)) { + if( dpo.son_budget.value > 0 && ((now - dpo.last_son_payout_time) >= fc::seconds(get_global_properties().parameters.son_pay_time()))) { uint64_t total_txs_signed = 0; share_type son_budget = dpo.son_budget; get_index_type().inspect_all_objects([this, &total_txs_signed](const object& o) { @@ -137,7 +138,8 @@ void database::pay_sons() if(s.txs_signed > 0){ auto son_params = get_global_properties().parameters; share_type pay = (s.txs_signed * son_budget.value)/total_txs_signed; - + // TODO: Remove me after QA + ilog( "pay ${p} to ${s} for ${t} transactions signed", ("p", pay.value)("s", s.id)("t",s.txs_signed) ); const auto& idx = get_index_type().indices().get(); auto son_obj = idx.find( s.owner ); modify( *son_obj, [&]( son_object& _son_obj) @@ -152,6 +154,7 @@ void database::pay_sons() //Reset the tx counter in each son statistics object modify( s, [&]( son_statistics_object& _s) { + _s.total_txs_signed += _s.txs_signed; _s.txs_signed = 0; }); } @@ -164,6 +167,156 @@ void database::pay_sons() } } +void database::update_son_metrics(const vector& curr_active_sons) +{ + vector current_sons; + + current_sons.reserve(curr_active_sons.size()); + std::transform(curr_active_sons.begin(), curr_active_sons.end(), + std::inserter(current_sons, current_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + const auto& son_idx = get_index_type().indices().get< by_id >(); + for( auto& son : son_idx ) + { + auto& stats = son.statistics(*this); + bool is_active_son = (std::find(current_sons.begin(), current_sons.end(), son.id) != current_sons.end()); + modify( stats, [&]( son_statistics_object& _stats ) + { + _stats.total_downtime += _stats.current_interval_downtime; + _stats.current_interval_downtime = 0; + if(is_active_son) + { + _stats.total_voted_time = _stats.total_voted_time + get_global_properties().parameters.maintenance_interval; + } + }); + } +} + +void database::update_son_statuses(const vector& curr_active_sons, const vector& new_active_sons) +{ + vector current_sons, new_sons; + vector sons_to_remove, sons_to_add; + const auto& idx = get_index_type().indices().get(); + + current_sons.reserve(curr_active_sons.size()); + std::transform(curr_active_sons.begin(), curr_active_sons.end(), + std::inserter(current_sons, current_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + new_sons.reserve(new_active_sons.size()); + std::transform(new_active_sons.begin(), new_active_sons.end(), + std::inserter(new_sons, new_sons.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + // find all cur_active_sons members that is not in new_active_sons + for_each(current_sons.begin(), current_sons.end(), + [&sons_to_remove, &new_sons](const son_id_type& si) + { + if(std::find(new_sons.begin(), new_sons.end(), si) == + new_sons.end()) + { + sons_to_remove.push_back(si); + } + } + ); + + for( const auto& sid : sons_to_remove ) + { + auto son = idx.find( sid ); + if(son == idx.end()) // SON is deleted already + continue; + // keep maintenance status for nodes becoming inactive + if(son->status == son_status::active) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::inactive; + }); + } + } + + // find all new_active_sons members that is not in cur_active_sons + for_each(new_sons.begin(), new_sons.end(), + [&sons_to_add, ¤t_sons](const son_id_type& si) + { + if(std::find(current_sons.begin(), current_sons.end(), si) == + current_sons.end()) + { + sons_to_add.push_back(si); + } + } + ); + + for( const auto& sid : sons_to_add ) + { + auto son = idx.find( sid ); + FC_ASSERT(son != idx.end(), "Invalid SON in active list, id={sonid}.", ("sonid", sid)); + // keep maintenance status for new nodes + if(son->status == son_status::inactive) + { + modify( *son, [&]( son_object& obj ){ + obj.status = son_status::active; + }); + } + } + + ilog("New SONS"); + for(size_t i = 0; i < new_sons.size(); i++) { + auto son = idx.find( new_sons[i] ); + if(son == idx.end()) // SON is deleted already + continue; + ilog( "${s}, status = ${ss}, total_votes = ${sv}", ("s", new_sons[i])("ss", son->status)("sv", son->total_votes) ); + } + + if( sons_to_remove.size() > 0 ) + { + remove_inactive_son_proposals(sons_to_remove); + } +} + +void database::update_son_wallet(const vector& new_active_sons) +{ + bool should_recreate_pw = true; + + // Expire for current son_wallet_object wallet, if exists + const auto& idx_swi = get_index_type().indices().get(); + auto obj = idx_swi.rbegin(); + if (obj != idx_swi.rend()) { + // Compare current wallet SONs and to-be lists of active sons + auto cur_wallet_sons = (*obj).sons; + + bool wallet_son_sets_equal = (cur_wallet_sons.size() == new_active_sons.size()); + if (wallet_son_sets_equal) { + for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { + wallet_son_sets_equal = wallet_son_sets_equal && cur_wallet_sons.at(i) == new_active_sons.at(i); + } + } + + should_recreate_pw = !wallet_son_sets_equal; + + if (should_recreate_pw) { + modify(*obj, [&, obj](son_wallet_object &swo) { + swo.expires = head_block_time(); + }); + } + } + + if (should_recreate_pw) { + // Create new son_wallet_object, to initiate wallet recreation + create( [&]( son_wallet_object& obj ) { + obj.valid_from = head_block_time(); + obj.expires = time_point_sec::maximum(); + obj.sons.insert(obj.sons.end(), new_active_sons.begin(), new_active_sons.end()); + }); + } +} + void database::pay_workers( share_type& budget ) { // ilog("Processing payroll! Available budget is ${b}", ("b", budget)); @@ -399,67 +552,116 @@ void database::update_active_sons() const auto& all_sons = get_index_type().indices(); + auto& local_vote_buffer_ref = _vote_tally_buffer; for( const son_object& son : all_sons ) { - modify( son, [&]( son_object& obj ){ - obj.total_votes = _vote_tally_buffer[son.vote_id]; + if(son.status == son_status::request_maintenance) + { + auto& stats = son.statistics(*this); + modify( stats, [&]( son_statistics_object& _s){ + _s.last_down_timestamp = head_block_time(); + }); + } + modify( son, [local_vote_buffer_ref]( son_object& obj ){ + obj.total_votes = local_vote_buffer_ref[obj.vote_id]; + if(obj.status == son_status::request_maintenance) + obj.status = son_status::in_maintenance; }); } // Update SON authority - modify( get(GRAPHENE_SON_ACCOUNT_ID), [&]( account_object& a ) + if( gpo.parameters.son_account() != GRAPHENE_NULL_ACCOUNT ) { - if( head_block_time() < HARDFORK_533_TIME ) + modify( get(gpo.parameters.son_account()), [&]( account_object& a ) { - uint64_t total_votes = 0; - map weights; - a.active.weight_threshold = 0; - a.active.clear(); - - for( const son_object& son : sons ) + if( head_block_time() < HARDFORK_533_TIME ) { - weights.emplace(son.son_account, _vote_tally_buffer[son.vote_id]); - total_votes += _vote_tally_buffer[son.vote_id]; - } + uint64_t total_votes = 0; + map weights; + a.active.weight_threshold = 0; + a.active.account_auths.clear(); - // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, - // then I want to keep the most significant 16 bits of what's left. - int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); - for( const auto& weight : weights ) + for( const son_object& son : sons ) + { + weights.emplace(son.son_account, _vote_tally_buffer[son.vote_id]); + total_votes += _vote_tally_buffer[son.vote_id]; + } + + // total_votes is 64 bits. Subtract the number of leading low bits from 64 to get the number of useful bits, + // then I want to keep the most significant 16 bits of what's left. + int8_t bits_to_drop = std::max(int(boost::multiprecision::detail::find_msb(total_votes)) - 15, 0); + for( const auto& weight : weights ) + { + // Ensure that everyone has at least one vote. Zero weights aren't allowed. + uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); + a.active.account_auths[weight.first] += votes; + a.active.weight_threshold += votes; + } + + a.active.weight_threshold *= 2; + a.active.weight_threshold /= 3; + a.active.weight_threshold += 1; + } + else { - // Ensure that everyone has at least one vote. Zero weights aren't allowed. - uint16_t votes = std::max((weight.second >> bits_to_drop), uint64_t(1) ); - a.active.account_auths[weight.first] += votes; - a.active.weight_threshold += votes; + vote_counter vc; + for( const son_object& son : sons ) + vc.add( son.son_account, std::max(_vote_tally_buffer[son.vote_id], UINT64_C(1)) ); + vc.finish_2_3( a.active ); } + } ); + } + - a.active.weight_threshold /= 2; - a.active.weight_threshold += 1; + // Compare current and to-be lists of active sons + auto cur_active_sons = gpo.active_sons; + vector new_active_sons; + for( const son_object& son : sons ) { + son_info swi; + swi.son_id = son.id; + swi.total_votes = son.total_votes; + swi.signing_key = son.signing_key; + swi.sidechain_public_keys = son.sidechain_public_keys; + new_active_sons.push_back(swi); + } + + bool son_sets_equal = (cur_active_sons.size() == new_active_sons.size()); + if (son_sets_equal) { + for( size_t i = 0; i < cur_active_sons.size(); i++ ) { + son_sets_equal = son_sets_equal && cur_active_sons.at(i) == new_active_sons.at(i); } - else - { - vote_counter vc; - for( const son_object& son : sons ) - vc.add( son.son_account, std::max(_vote_tally_buffer[son.vote_id], UINT64_C(1)) ); - vc.finish( a.active ); - } - } ); + } + + if (son_sets_equal) { + ilog( "Active SONs set NOT CHANGED" ); + } else { + ilog( "Active SONs set CHANGED" ); + + update_son_wallet(new_active_sons); + update_son_statuses(cur_active_sons, new_active_sons); + } + + // Update son performance metrics + update_son_metrics(cur_active_sons); modify(gpo, [&]( global_property_object& gp ){ gp.active_sons.clear(); - gp.active_sons.reserve(sons.size()); - std::transform(sons.begin(), sons.end(), - std::inserter(gp.active_sons, gp.active_sons.end()), - [](const son_object& s) { - return s.id; - }); + gp.active_sons.reserve(new_active_sons.size()); + gp.active_sons.insert(gp.active_sons.end(), new_active_sons.begin(), new_active_sons.end()); }); - //const witness_schedule_object& wso = witness_schedule_id_type()(*this); - //modify(wso, [&](witness_schedule_object& _wso) - //{ - // _wso.scheduler.update(gpo.active_witnesses); - //}); + const son_schedule_object& sso = son_schedule_id_type()(*this); + modify(sso, [&](son_schedule_object& _sso) + { + flat_set active_sons; + active_sons.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_sons, active_sons.end()), + [](const son_info& swi) { + return swi.son_id; + }); + _sso.scheduler.update(active_sons); + }); } FC_CAPTURE_AND_RETHROW() } void database::initialize_budget_record( fc::time_point_sec now, budget_record& rec )const @@ -1375,6 +1577,30 @@ void process_dividend_assets(database& db) } } FC_CAPTURE_AND_RETHROW() } +void perform_son_tasks(database& db) +{ + const global_property_object& gpo = db.get_global_properties(); + if(gpo.parameters.son_account() == GRAPHENE_NULL_ACCOUNT && db.head_block_time() >= HARDFORK_SON_TIME) + { + const auto& son_account = db.create([&](account_object& a) { + a.name = "son-account"; + a.statistics = db.create([&](account_statistics_object& s){s.owner = a.id;}).id; + a.owner.weight_threshold = 1; + a.active.weight_threshold = 0; + a.registrar = a.lifetime_referrer = a.referrer = a.id; + a.membership_expiration_date = time_point_sec::maximum(); + a.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + a.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }); + + db.modify( gpo, [&]( global_property_object& gpo ) { + gpo.parameters.extensions.value.son_account = son_account.get_id(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.son_account = son_account.get_id(); + }); + } +} + void database::perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props) { try { const auto& gpo = get_global_properties(); @@ -1383,6 +1609,7 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g create_buyback_orders(*this); process_dividend_assets(*this); + perform_son_tasks(*this); struct vote_tally_helper { database& d; diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 029a55d4..f6d164d2 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -40,6 +40,7 @@ database::database() : { initialize_indexes(); initialize_evaluators(); + initialize_db_sidechain(); } database::~database() diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 63b0fce8..db245f0a 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -294,6 +294,51 @@ struct get_impacted_account_visitor void operator()( const son_delete_operation& op ) { _impacted.insert( op.owner_account ); } + void operator()( const son_heartbeat_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_report_down_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_maintenance_operation& op ) { + _impacted.insert( op.owner_account ); + } + void operator()( const son_wallet_recreate_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_update_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_deposit_process_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const son_wallet_withdraw_process_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_address_add_operation& op ) { + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_address_update_operation& op ) { + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_address_delete_operation& op ) { + _impacted.insert( op.sidechain_address_account ); + } + void operator()( const sidechain_transaction_create_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_sign_operation& op ) { + _impacted.insert( op.payer ); + } + void operator()( const sidechain_transaction_send_operation& op ) { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) @@ -387,6 +432,19 @@ void get_relevant_accounts( const object* obj, flat_set& accoun assert( aobj != nullptr ); accounts.insert( aobj->son_account ); break; + } case son_wallet_object_type:{ + break; + } case son_wallet_deposit_object_type:{ + break; + } case son_wallet_withdraw_object_type:{ + break; + } case sidechain_address_object_type:{ + const auto& aobj = dynamic_cast(obj); + assert( aobj != nullptr ); + accounts.insert( aobj->sidechain_address_account ); + break; + } case sidechain_transaction_object_type:{ + break; } } } diff --git a/libraries/chain/db_sidechain.cpp b/libraries/chain/db_sidechain.cpp new file mode 100644 index 00000000..77594b3f --- /dev/null +++ b/libraries/chain/db_sidechain.cpp @@ -0,0 +1,10 @@ +#include + +namespace graphene { namespace chain { + +void database::initialize_db_sidechain() +{ + recreate_primary_wallet = false; +} + +} } diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index 7df02a39..ed440d82 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -224,6 +224,7 @@ void database::clear_expired_proposals() elog("Failed to apply proposed transaction on its expiration. Deleting it.\n${proposal}\n${error}", ("proposal", proposal)("error", e.to_detail_string())); } + remove_son_proposal(proposal); remove(proposal); } } @@ -651,4 +652,50 @@ void database::update_betting_markets(fc::time_point_sec current_block_time) remove_completed_events(); } +void database::remove_son_proposal( const proposal_object& proposal ) +{ try { + if( proposal.proposed_transaction.operations.size() == 1 && + ( proposal.proposed_transaction.operations.back().which() == operation::tag::value || + proposal.proposed_transaction.operations.back().which() == operation::tag::value) ) + { + const auto& son_proposal_idx = get_index_type().indices().get(); + auto son_proposal_itr = son_proposal_idx.find( proposal.id ); + if( son_proposal_itr == son_proposal_idx.end() ) { + return; + } + remove( *son_proposal_itr ); + } +} FC_CAPTURE_AND_RETHROW( (proposal) ) } + +void database::remove_inactive_son_down_proposals( const vector& son_ids_to_remove ) +{ + const auto& son_proposal_idx = get_index_type().indices().get< by_id >(); + std::vector proposals_to_remove; + + for( auto& son_proposal : son_proposal_idx ) + { + if(son_proposal.proposal_type == son_proposal_type::son_report_down_proposal) + { + auto it = std::find(son_ids_to_remove.begin(), son_ids_to_remove.end(), son_proposal.son_id); + if (it != son_ids_to_remove.end()) + { + ilog( "Removing inactive proposal ${p} for son ${s}", ("p", son_proposal.proposal_id) ("s",son_proposal.son_id)); + proposals_to_remove.push_back(son_proposal.proposal_id); + } + } + } + + for( auto& proposal_id : proposals_to_remove ) + { + const auto& proposal_obj = proposal_id(*this); + remove_son_proposal(proposal_obj); + remove(proposal_obj); + } +} + +void database::remove_inactive_son_proposals( const vector& son_ids_to_remove ) +{ + remove_inactive_son_down_proposals( son_ids_to_remove ); +} + } } diff --git a/libraries/chain/db_witness_schedule.cpp b/libraries/chain/db_witness_schedule.cpp index e12c81dc..31caad4b 100644 --- a/libraries/chain/db_witness_schedule.cpp +++ b/libraries/chain/db_witness_schedule.cpp @@ -26,6 +26,8 @@ #include #include #include +#include +#include namespace graphene { namespace chain { @@ -72,6 +74,47 @@ witness_id_type database::get_scheduled_witness( uint32_t slot_num )const return wid; } +son_id_type database::get_scheduled_son( uint32_t slot_num )const +{ + son_id_type sid; + const global_property_object& gpo = get_global_properties(); + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SHUFFLED_ALGORITHM) + { + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + const son_schedule_object& sso = son_schedule_id_type()(*this); + uint64_t current_aslot = dpo.current_aslot + slot_num; + return sso.current_shuffled_sons[ current_aslot % sso.current_shuffled_sons.size() ]; + } + if (gpo.parameters.witness_schedule_algorithm == GRAPHENE_WITNESS_SCHEDULED_ALGORITHM && + slot_num != 0 ) + { + const son_schedule_object& sso = son_schedule_id_type()(*this); + // ask the near scheduler who goes in the given slot + bool slot_is_near = sso.scheduler.get_slot(slot_num-1, sid); + if(! slot_is_near) + { + // if the near scheduler doesn't know, we have to extend it to + // a far scheduler. + // n.b. instantiating it is slow, but block gaps long enough to + // need it are likely pretty rare. + + witness_scheduler_rng far_rng(sso.rng_seed.begin(), GRAPHENE_FAR_SCHEDULE_CTR_IV); + + far_future_son_scheduler far_scheduler = + far_future_son_scheduler(sso.scheduler, far_rng); + if(!far_scheduler.get_slot(slot_num-1, sid)) + { + // no scheduled son -- somebody set up us the bomb + // n.b. this code path is impossible, the present + // implementation of far_future_son_scheduler + // returns true unconditionally + assert( false ); + } + } + } + return sid; +} + fc::time_point_sec database::get_slot_time(uint32_t slot_num)const { if( slot_num == 0 ) @@ -146,6 +189,41 @@ void database::update_witness_schedule() } } +void database::update_son_schedule() +{ + const son_schedule_object& sso = son_schedule_id_type()(*this); + const global_property_object& gpo = get_global_properties(); + + if( head_block_num() % gpo.active_sons.size() == 0 ) + { + modify( sso, [&]( son_schedule_object& _sso ) + { + _sso.current_shuffled_sons.clear(); + _sso.current_shuffled_sons.reserve( gpo.active_sons.size() ); + + for( const son_info& w : gpo.active_sons ) + _sso.current_shuffled_sons.push_back( w.son_id ); + + auto now_hi = uint64_t(head_block_time().sec_since_epoch()) << 32; + for( uint32_t i = 0; i < _sso.current_shuffled_sons.size(); ++i ) + { + /// High performance random generator + /// http://xorshift.di.unimi.it/ + uint64_t k = now_hi + uint64_t(i)*2685821657736338717ULL; + k ^= (k >> 12); + k ^= (k << 25); + k ^= (k >> 27); + k *= 2685821657736338717ULL; + + uint32_t jmax = _sso.current_shuffled_sons.size() - i; + uint32_t j = i + k%jmax; + std::swap( _sso.current_shuffled_sons[i], + _sso.current_shuffled_sons[j] ); + } + }); + } +} + vector database::get_near_witness_schedule()const { const witness_schedule_object& wso = witness_schedule_id_type()(*this); @@ -226,6 +304,71 @@ void database::update_witness_schedule(const signed_block& next_block) idump( ( double(total_time/1000000.0)/calls) ); } +void database::update_son_schedule(const signed_block& next_block) +{ + auto start = fc::time_point::now(); + const global_property_object& gpo = get_global_properties(); + const son_schedule_object& sso = get(son_schedule_id_type()); + uint32_t schedule_needs_filled = gpo.active_sons.size(); + uint32_t schedule_slot = get_slot_at_time(next_block.timestamp); + + // We shouldn't be able to generate _pending_block with timestamp + // in the past, and incoming blocks from the network with timestamp + // in the past shouldn't be able to make it this far without + // triggering FC_ASSERT elsewhere + + assert( schedule_slot > 0 ); + + son_id_type first_son; + bool slot_is_near = sso.scheduler.get_slot( schedule_slot-1, first_son ); + + son_id_type son; + + const dynamic_global_property_object& dpo = get_dynamic_global_properties(); + + assert( dpo.random.data_size() == witness_scheduler_rng::seed_length ); + assert( witness_scheduler_rng::seed_length == sso.rng_seed.size() ); + + modify(sso, [&](son_schedule_object& _sso) + { + _sso.slots_since_genesis += schedule_slot; + witness_scheduler_rng rng(sso.rng_seed.data, _sso.slots_since_genesis); + + _sso.scheduler._min_token_count = std::max(int(gpo.active_sons.size()) / 2, 1); + + if( slot_is_near ) + { + uint32_t drain = schedule_slot; + while( drain > 0 ) + { + if( _sso.scheduler.size() == 0 ) + break; + _sso.scheduler.consume_schedule(); + --drain; + } + } + else + { + _sso.scheduler.reset_schedule( first_son ); + } + while( !_sso.scheduler.get_slot(schedule_needs_filled, son) ) + { + if( _sso.scheduler.produce_schedule(rng) & emit_turn ) + memcpy(_sso.rng_seed.begin(), dpo.random.data(), dpo.random.data_size()); + } + _sso.last_scheduling_block = next_block.block_num(); + _sso.recent_slots_filled = ( + (_sso.recent_slots_filled << 1) + + 1) << (schedule_slot - 1); + }); + auto end = fc::time_point::now(); + static uint64_t total_time = 0; + static uint64_t calls = 0; + total_time += (end - start).count(); + if( ++calls % 1000 == 0 ) + idump( ( double(total_time/1000000.0)/calls) ); +} + uint32_t database::update_witness_missed_blocks( const signed_block& b ) { uint32_t missed_blocks = get_slot_at_time( b.timestamp ); diff --git a/libraries/chain/get_config.cpp b/libraries/chain/get_config.cpp index c961b950..245d6598 100644 --- a/libraries/chain/get_config.cpp +++ b/libraries/chain/get_config.cpp @@ -108,6 +108,11 @@ fc::variant_object get_config() result[ "GRAPHENE_RELAXED_COMMITTEE_ACCOUNT" ] = fc::variant(GRAPHENE_RELAXED_COMMITTEE_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); result[ "GRAPHENE_NULL_ACCOUNT" ] = fc::variant(GRAPHENE_NULL_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); result[ "GRAPHENE_TEMP_ACCOUNT" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_PROXY_TO_SELF_ACCOUNT" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_RAKE_FEE_ACCOUNT_ID" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_NULL_WITNESS" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_FBA_STEALTH_DESIGNATED_ASSET" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); + result[ "GRAPHENE_DEFAULT_RAKE_FEE_PERCENTAGE" ] = fc::variant(GRAPHENE_TEMP_ACCOUNT, GRAPHENE_MAX_NESTED_OBJECTS); return result; } diff --git a/libraries/chain/hardfork.d/SON.hf b/libraries/chain/hardfork.d/SON.hf index 355c5b96..5c4e1e76 100644 --- a/libraries/chain/hardfork.d/SON.hf +++ b/libraries/chain/hardfork.d/SON.hf @@ -1,5 +1,6 @@ -// SON HARDFORK Monday, September 21, 2020 1:43:11 PM +// SON HARDFORK Wednesday, January 1, 2020 12:00:00 AM - 1577836800 +// SON HARDFORK Monday, September 21, 2020 1:43:11 PM - 1600695791 #ifndef HARDFORK_SON_TIME #include -#define HARDFORK_SON_TIME (fc::time_point_sec( time(NULL) - (60 * 60) )) +#define HARDFORK_SON_TIME (fc::time_point_sec( 1577836800 )) #endif diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index 37b0885d..dfd80f03 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -175,8 +175,6 @@ #define GRAPHENE_PROXY_TO_SELF_ACCOUNT (graphene::chain::account_id_type(5)) /// #define GRAPHENE_RAKE_FEE_ACCOUNT_ID (graphene::chain::account_id_type(6)) -/// -#define GRAPHENE_SON_ACCOUNT_ID (graphene::chain::account_id_type(7)) /// Sentinel value used in the scheduler. #define GRAPHENE_NULL_WITNESS (graphene::chain::witness_id_type(0)) ///@} @@ -234,7 +232,10 @@ #define MIN_SON_MEMBER_COUNT 15 #define SON_VESTING_AMOUNT (50*GRAPHENE_BLOCKCHAIN_PRECISION) // 50 PPY #define SON_VESTING_PERIOD (60*60*24*2) // 2 days -#define SON_DEREGISTER_TIME (12) // 12 Hours +#define SON_DEREGISTER_TIME (60*60*12) // 12 Hours in seconds +#define SON_HEARTBEAT_FREQUENCY (60*3) // 3 minutes in seconds +#define SON_DOWN_TIME (60*3*2) // 2 Heartbeats in seconds +#define SON_PAY_TIME (60*60*24) // 1 day #define MIN_SON_PAY_DAILY_MAX (GRAPHENE_BLOCKCHAIN_PRECISION * int64_t(200)) #define SWEEPS_DEFAULT_DISTRIBUTION_PERCENTAGE (2*GRAPHENE_1_PERCENT) #define SWEEPS_DEFAULT_DISTRIBUTION_ASSET (graphene::chain::asset_id_type(0)) diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index 1e989a21..857c6dab 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -40,6 +40,8 @@ #include +#include + #include #include @@ -240,6 +242,22 @@ namespace graphene { namespace chain { */ witness_id_type get_scheduled_witness(uint32_t slot_num)const; + /** + * @brief Get the son scheduled for block production in a slot. + * + * slot_num always corresponds to a time in the future. + * + * If slot_num == 1, returns the next scheduled son. + * If slot_num == 2, returns the next scheduled son after + * 1 block gap. + * + * Use the get_slot_time() and get_slot_at_time() functions + * to convert between slot_num and timestamp. + * + * Passing slot_num == 0 returns GRAPHENE_NULL_WITNESS + */ + son_id_type get_scheduled_son(uint32_t slot_num)const; + /** * Get the time at which the given slot occurs. * @@ -263,6 +281,8 @@ namespace graphene { namespace chain { vector get_near_witness_schedule()const; void update_witness_schedule(); void update_witness_schedule(const signed_block& next_block); + void update_son_schedule(); + void update_son_schedule(const signed_block& next_block); void check_lottery_end_by_participants( asset_id_type asset_id ); void check_ending_lotteries(); @@ -280,12 +300,11 @@ namespace graphene { namespace chain { std::vector get_seeds( asset_id_type for_asset, uint8_t count_winners )const; uint64_t get_random_bits( uint64_t bound ); std::set get_sons_being_deregistered(); + std::set get_sons_being_reported_down(); std::set get_sons_to_be_deregistered(); - fc::optional create_son_deregister_proposal(const son_id_type& son_id, const witness_object& current_witness ); + fc::optional create_son_deregister_proposal( son_id_type son_id, account_id_type paying_son ); signed_transaction create_signed_transaction( const fc::ecc::private_key& signing_private_key, const operation& op ); - void process_son_proposals( const witness_object& current_witness, const fc::ecc::private_key& private_key ); - void remove_son_proposal( const proposal_object& proposal ); - bool is_son_dereg_valid( const son_id_type& son_id ); + bool is_son_dereg_valid( son_id_type son_id ); time_point_sec head_block_time()const; uint32_t head_block_num()const; @@ -527,7 +546,13 @@ namespace graphene { namespace chain { void perform_chain_maintenance(const signed_block& next_block, const global_property_object& global_props); void update_active_witnesses(); void update_active_committee_members(); + void update_son_metrics( const vector& curr_active_sons ); void update_active_sons(); + void remove_son_proposal( const proposal_object& proposal ); + void remove_inactive_son_down_proposals( const vector& son_ids_to_remove ); + void remove_inactive_son_proposals( const vector& son_ids_to_remove ); + void update_son_statuses( const vector& cur_active_sons, const vector& new_active_sons ); + void update_son_wallet( const vector& new_active_sons ); void update_worker_votes(); template @@ -582,6 +607,13 @@ namespace graphene { namespace chain { * database::close() has not been called, or failed during execution. */ bool _opened = false; + + /////////////////////// db_sidechain.cpp //////////////////// + public: + bool recreate_primary_wallet; + void initialize_db_sidechain(); + protected: + private: }; namespace detail diff --git a/libraries/chain/include/graphene/chain/global_property_object.hpp b/libraries/chain/include/graphene/chain/global_property_object.hpp index 130648e9..c34265cb 100644 --- a/libraries/chain/include/graphene/chain/global_property_object.hpp +++ b/libraries/chain/include/graphene/chain/global_property_object.hpp @@ -27,6 +27,7 @@ #include #include #include +#include #include namespace graphene { namespace chain { @@ -51,7 +52,7 @@ namespace graphene { namespace chain { uint32_t next_available_vote_id = 0; vector active_committee_members; // updated once per maintenance interval flat_set active_witnesses; // updated once per maintenance interval - vector active_sons; // updated once per maintenance interval + vector active_sons; // updated once per maintenance interval }; /** @@ -78,7 +79,7 @@ namespace graphene { namespace chain { time_point_sec last_budget_time; share_type witness_budget; //Last SON Payout time, it can be different to the maintenance interval time - time_point_sec last_son_payout_time; + time_point_sec last_son_payout_time = HARDFORK_SON_TIME; share_type son_budget = 0; uint32_t accounts_registered_this_interval = 0; /** @@ -137,6 +138,8 @@ FC_REFLECT_DERIVED( graphene::chain::dynamic_global_property_object, (graphene:: (next_maintenance_time) (last_budget_time) (witness_budget) + (last_son_payout_time) + (son_budget) (accounts_registered_this_interval) (recently_missed_count) (current_aslot) diff --git a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp index a7b76471..7eb32489 100644 --- a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp @@ -43,6 +43,7 @@ namespace graphene { namespace chain { void operator()( const T &v ) const {} void operator()( const son_delete_operation &v ); + void operator()( const son_report_down_operation &v ); }; class proposal_create_evaluator : public evaluator diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index c62274ba..4b091726 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -46,6 +46,11 @@ namespace graphene { namespace chain { optional < uint32_t > son_vesting_amount; optional < uint32_t > son_vesting_period; optional < uint32_t > son_pay_daily_max; + optional < uint32_t > son_pay_time; + optional < uint32_t > son_deregister_time; + optional < uint32_t > son_heartbeat_frequency; + optional < uint32_t > son_down_time; + optional < account_id_type > son_account; }; struct chain_parameters @@ -138,6 +143,21 @@ namespace graphene { namespace chain { inline uint16_t son_pay_daily_max()const { return extensions.value.son_pay_daily_max.valid() ? *extensions.value.son_pay_daily_max : MIN_SON_PAY_DAILY_MAX; } + inline uint16_t son_pay_time()const { + return extensions.value.son_pay_time.valid() ? *extensions.value.son_pay_time : SON_PAY_TIME; + } + inline uint16_t son_deregister_time()const { + return extensions.value.son_deregister_time.valid() ? *extensions.value.son_deregister_time : SON_DEREGISTER_TIME; + } + inline uint16_t son_heartbeat_frequency()const { + return extensions.value.son_heartbeat_frequency.valid() ? *extensions.value.son_heartbeat_frequency : SON_HEARTBEAT_FREQUENCY; + } + inline uint16_t son_down_time()const { + return extensions.value.son_down_time.valid() ? *extensions.value.son_down_time : SON_DOWN_TIME; + } + inline account_id_type son_account() const { + return extensions.value.son_account.valid() ? *extensions.value.son_account : GRAPHENE_NULL_ACCOUNT; + } }; } } // graphene::chain @@ -155,6 +175,11 @@ FC_REFLECT( graphene::chain::parameter_extension, (son_vesting_amount) (son_vesting_period) (son_pay_daily_max) + (son_pay_time) + (son_deregister_time) + (son_heartbeat_frequency) + (son_down_time) + (son_account) ) FC_REFLECT( graphene::chain::chain_parameters, diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index cbad1e05..24bb7ba5 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -46,6 +46,11 @@ #include #include #include +#include +#include +#include +#include +#include namespace graphene { namespace chain { @@ -139,7 +144,22 @@ namespace graphene { namespace chain { sweeps_vesting_claim_operation, son_create_operation, son_update_operation, - son_delete_operation + son_delete_operation, + son_heartbeat_operation, + son_report_down_operation, + son_maintenance_operation, + son_wallet_recreate_operation, + son_wallet_update_operation, + son_wallet_deposit_create_operation, + son_wallet_deposit_process_operation, + son_wallet_withdraw_create_operation, + son_wallet_withdraw_process_operation, + sidechain_address_add_operation, + sidechain_address_update_operation, + sidechain_address_delete_operation, + sidechain_transaction_create_operation, + sidechain_transaction_sign_operation, + sidechain_transaction_send_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp new file mode 100644 index 00000000..e79f65de --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_address.hpp @@ -0,0 +1,64 @@ +#pragma once +#include + +#include + +namespace graphene { namespace chain { + + struct sidechain_address_add_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type sidechain_address_account; + graphene::peerplays_sidechain::sidechain_type sidechain; + string deposit_address; + string withdraw_address; + + account_id_type fee_payer()const { return sidechain_address_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_address_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + sidechain_address_id_type sidechain_address_id; + account_id_type sidechain_address_account; + graphene::peerplays_sidechain::sidechain_type sidechain; + optional deposit_address; + optional withdraw_address; + + account_id_type fee_payer()const { return sidechain_address_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_address_delete_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + sidechain_address_id_type sidechain_address_id; + account_id_type sidechain_address_account; + graphene::peerplays_sidechain::sidechain_type sidechain; + + account_id_type fee_payer()const { return sidechain_address_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::sidechain_address_add_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_add_operation, (fee) + (sidechain_address_account)(sidechain)(deposit_address)(withdraw_address) ) + +FC_REFLECT(graphene::chain::sidechain_address_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_update_operation, (fee) + (sidechain_address_id) + (sidechain_address_account)(sidechain)(deposit_address)(withdraw_address) ) + +FC_REFLECT(graphene::chain::sidechain_address_delete_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::sidechain_address_delete_operation, (fee) + (sidechain_address_id) + (sidechain_address_account)(sidechain) ) diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp new file mode 100644 index 00000000..4ddbd7ce --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain_transaction.hpp @@ -0,0 +1,71 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct sidechain_transaction_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_type sidechain; + object_id_type object_id; + std::string transaction; + std::vector signers; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct sidechain_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_transaction_id_type sidechain_transaction_id; + std::string transaction; + block_id_type block; + bool complete; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct sidechain_transaction_send_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + sidechain_transaction_id_type sidechain_transaction_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::sidechain_transaction_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_create_operation, (fee)(payer) + (sidechain) + (object_id) + (transaction) + (signers) ) + +FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_sign_operation, (fee)(payer) + (sidechain_transaction_id) + (transaction) + (block) + (complete) ) + +FC_REFLECT( graphene::chain::sidechain_transaction_send_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::sidechain_transaction_send_operation, (fee)(payer) + (sidechain_transaction_id) ) \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/protocol/son.hpp b/libraries/chain/include/graphene/chain/protocol/son.hpp index 93b8a0a4..3dfbc52d 100644 --- a/libraries/chain/include/graphene/chain/protocol/son.hpp +++ b/libraries/chain/include/graphene/chain/protocol/son.hpp @@ -1,5 +1,6 @@ #pragma once #include +#include namespace graphene { namespace chain { @@ -12,6 +13,7 @@ namespace graphene { namespace chain { std::string url; vesting_balance_id_type deposit; public_key_type signing_key; + flat_map sidechain_public_keys; vesting_balance_id_type pay_vb; account_id_type fee_payer()const { return owner_account; } @@ -28,6 +30,7 @@ namespace graphene { namespace chain { optional new_url; optional new_deposit; optional new_signing_key; + optional> new_sidechain_public_keys; optional new_pay_vb; account_id_type fee_payer()const { return owner_account; } @@ -47,15 +50,71 @@ namespace graphene { namespace chain { share_type calculate_fee(const fee_parameters_type& k)const { return 0; } }; + struct son_heartbeat_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + time_point_sec ts; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + + struct son_report_down_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type payer; + time_point_sec down_ts; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + enum class son_maintenance_request_type + { + request_maintenance, + cancel_request_maintenance + }; + + struct son_maintenance_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + son_id_type son_id; + account_id_type owner_account; + son_maintenance_request_type request_type = son_maintenance_request_type::request_maintenance; + + account_id_type fee_payer()const { return owner_account; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + } } // namespace graphene::chain FC_REFLECT(graphene::chain::son_create_operation::fee_parameters_type, (fee) ) -FC_REFLECT(graphene::chain::son_create_operation, (fee)(owner_account)(url)(deposit)(signing_key) +FC_REFLECT(graphene::chain::son_create_operation, (fee)(owner_account)(url)(deposit)(signing_key)(sidechain_public_keys) (pay_vb) ) FC_REFLECT(graphene::chain::son_update_operation::fee_parameters_type, (fee) ) FC_REFLECT(graphene::chain::son_update_operation, (fee)(son_id)(owner_account)(new_url)(new_deposit) - (new_signing_key)(new_pay_vb) ) + (new_signing_key)(new_sidechain_public_keys)(new_pay_vb) ) FC_REFLECT(graphene::chain::son_delete_operation::fee_parameters_type, (fee) ) FC_REFLECT(graphene::chain::son_delete_operation, (fee)(son_id)(payer)(owner_account) ) + +FC_REFLECT(graphene::chain::son_heartbeat_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_heartbeat_operation, (fee)(son_id)(owner_account)(ts) ) + +FC_REFLECT(graphene::chain::son_report_down_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_report_down_operation, (fee)(son_id)(payer)(down_ts) ) + +FC_REFLECT_ENUM( graphene::chain::son_maintenance_request_type, (request_maintenance)(cancel_request_maintenance) ) +FC_REFLECT(graphene::chain::son_maintenance_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_maintenance_operation, (fee)(son_id)(owner_account)(request_type) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp new file mode 100644 index 00000000..f41cfa1f --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet.hpp @@ -0,0 +1,40 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + + struct son_wallet_recreate_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + vector sons; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_update_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_id_type son_wallet_id; + graphene::peerplays_sidechain::sidechain_type sidechain; + string address; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_recreate_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_recreate_operation, (fee)(payer)(sons) ) +FC_REFLECT(graphene::chain::son_wallet_update_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_update_operation, (fee)(payer)(son_wallet_id)(sidechain)(address) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp new file mode 100644 index 00000000..a1b44fac --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet_deposit.hpp @@ -0,0 +1,56 @@ +#pragma once +#include +#include + +#include + +namespace graphene { namespace chain { + + struct son_wallet_deposit_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_id_type son_id; + fc::time_point_sec timestamp; + sidechain_type sidechain; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + fc::safe sidechain_amount; + chain::account_id_type peerplays_from; + chain::account_id_type peerplays_to; + chain::asset peerplays_asset; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_deposit_process_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_deposit_id_type son_wallet_deposit_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_deposit_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_deposit_create_operation, (fee)(payer) + (son_id) (timestamp) (sidechain) + (sidechain_uid) (sidechain_transaction_id) (sidechain_from) (sidechain_to) (sidechain_currency) (sidechain_amount) + (peerplays_from) (peerplays_to) (peerplays_asset) ) + +FC_REFLECT(graphene::chain::son_wallet_deposit_process_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_deposit_process_operation, (fee)(payer) + (son_wallet_deposit_id) ) diff --git a/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp b/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp new file mode 100644 index 00000000..353d695e --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/son_wallet_withdraw.hpp @@ -0,0 +1,55 @@ +#pragma once +#include +#include + +#include + +namespace graphene { namespace chain { + + struct son_wallet_withdraw_create_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_id_type son_id; + fc::time_point_sec timestamp; + sidechain_type sidechain; + std::string peerplays_uid; + std::string peerplays_transaction_id; + chain::account_id_type peerplays_from; + chain::asset peerplays_asset; + sidechain_type withdraw_sidechain; + std::string withdraw_address; + std::string withdraw_currency; + safe withdraw_amount; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + + struct son_wallet_withdraw_process_operation : public base_operation + { + struct fee_parameters_type { uint64_t fee = 0; }; + + asset fee; + account_id_type payer; + + son_wallet_withdraw_id_type son_wallet_withdraw_id; + + account_id_type fee_payer()const { return payer; } + share_type calculate_fee(const fee_parameters_type& k)const { return 0; } + }; + +} } // namespace graphene::chain + +FC_REFLECT(graphene::chain::son_wallet_withdraw_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_withdraw_create_operation, (fee)(payer) + (son_id) (timestamp) (sidechain) + (peerplays_uid) (peerplays_transaction_id) (peerplays_from) (peerplays_asset) + (withdraw_sidechain) (withdraw_address) (withdraw_currency) (withdraw_amount) ) + +FC_REFLECT(graphene::chain::son_wallet_withdraw_process_operation::fee_parameters_type, (fee) ) +FC_REFLECT(graphene::chain::son_wallet_withdraw_process_operation, (fee)(payer) + (son_wallet_withdraw_id) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index bcdd1a83..63863ca1 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -147,6 +147,11 @@ namespace graphene { namespace chain { bet_object_type, son_object_type, son_proposal_object_type, + son_wallet_object_type, + son_wallet_deposit_object_type, + son_wallet_withdraw_object_type, + sidechain_address_object_type, + sidechain_transaction_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -176,7 +181,8 @@ namespace graphene { namespace chain { impl_global_betting_statistics_object_type, impl_lottery_balance_object_type, impl_sweeps_vesting_balance_object_type, - impl_son_statistics_object_type + impl_son_statistics_object_type, + impl_son_schedule_object_type }; //typedef fc::unsigned_int object_id_type; @@ -209,6 +215,11 @@ namespace graphene { namespace chain { class bet_object; class son_object; class son_proposal_object; + class son_wallet_object; + class son_wallet_deposit_object; + class son_wallet_withdraw_object; + class sidechain_address_object; + class sidechain_transaction_object; typedef object_id< protocol_ids, account_object_type, account_object> account_id_type; typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type; @@ -237,6 +248,11 @@ namespace graphene { namespace chain { typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type; typedef object_id< protocol_ids, son_object_type, son_object> son_id_type; typedef object_id< protocol_ids, son_proposal_object_type, son_proposal_object> son_proposal_id_type; + typedef object_id< protocol_ids, son_wallet_object_type, son_wallet_object> son_wallet_id_type; + typedef object_id< protocol_ids, son_wallet_deposit_object_type, son_wallet_deposit_object> son_wallet_deposit_id_type; + typedef object_id< protocol_ids, son_wallet_withdraw_object_type, son_wallet_withdraw_object> son_wallet_withdraw_id_type; + typedef object_id< protocol_ids, sidechain_address_object_type, sidechain_address_object> sidechain_address_id_type; + typedef object_id< protocol_ids, sidechain_transaction_object_type,sidechain_transaction_object> sidechain_transaction_id_type; // implementation types class global_property_object; @@ -261,6 +277,7 @@ namespace graphene { namespace chain { class lottery_balance_object; class sweeps_vesting_balance_object; class son_statistics_object; + class son_schedule_object; typedef object_id< implementation_ids, impl_global_property_object_type, global_property_object> global_property_id_type; typedef object_id< implementation_ids, impl_dynamic_global_property_object_type, dynamic_global_property_object> dynamic_global_property_id_type; @@ -290,6 +307,7 @@ namespace graphene { namespace chain { typedef object_id< implementation_ids, impl_lottery_balance_object_type, lottery_balance_object > lottery_balance_id_type; typedef object_id< implementation_ids, impl_sweeps_vesting_balance_object_type, sweeps_vesting_balance_object> sweeps_vesting_balance_id_type; typedef object_id< implementation_ids, impl_son_statistics_object_type, son_statistics_object > son_statistics_id_type; + typedef object_id< implementation_ids, impl_son_schedule_object_type, son_schedule_object> son_schedule_id_type; typedef fc::array symbol_type; typedef fc::ripemd160 block_id_type; @@ -421,6 +439,11 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (bet_object_type) (son_object_type) (son_proposal_object_type) + (son_wallet_object_type) + (son_wallet_deposit_object_type) + (son_wallet_withdraw_object_type) + (sidechain_address_object_type) + (sidechain_transaction_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -449,6 +472,7 @@ FC_REFLECT_ENUM( graphene::chain::impl_object_type, (impl_lottery_balance_object_type) (impl_sweeps_vesting_balance_object_type) (impl_son_statistics_object_type) + (impl_son_schedule_object_type) ) FC_REFLECT_TYPENAME( graphene::chain::share_type ) @@ -493,6 +517,11 @@ FC_REFLECT_TYPENAME( graphene::chain::global_betting_statistics_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_details_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_id_type ) FC_REFLECT_TYPENAME( graphene::chain::son_proposal_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_deposit_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::son_wallet_withdraw_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::sidechain_address_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::sidechain_transaction_id_type ) FC_REFLECT( graphene::chain::void_t, ) diff --git a/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp new file mode 100644 index 00000000..82bfcf1a --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_address_evaluator.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class add_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_add_operation operation_type; + + void_result do_evaluate(const sidechain_address_add_operation& o); + object_id_type do_apply(const sidechain_address_add_operation& o); +}; + +class update_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_update_operation operation_type; + + void_result do_evaluate(const sidechain_address_update_operation& o); + object_id_type do_apply(const sidechain_address_update_operation& o); +}; + +class delete_sidechain_address_evaluator : public evaluator +{ +public: + typedef sidechain_address_delete_operation operation_type; + + void_result do_evaluate(const sidechain_address_delete_operation& o); + void_result do_apply(const sidechain_address_delete_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/sidechain_address_object.hpp b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp new file mode 100644 index 00000000..86e1b16d --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_address_object.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include + +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class sidechain_address_object + * @brief tracks information about a sidechain addresses for user accounts. + * @ingroup object + */ + class sidechain_address_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = sidechain_address_object_type; + + account_id_type sidechain_address_account; + graphene::peerplays_sidechain::sidechain_type sidechain; + string deposit_address; + string withdraw_address; + + sidechain_address_object() : + sidechain(graphene::peerplays_sidechain::sidechain_type::bitcoin), + deposit_address(""), + withdraw_address("") {} + }; + + struct by_account; + struct by_sidechain; + struct by_account_and_sidechain; + struct by_sidechain_and_deposit_address; + using sidechain_address_multi_index_type = multi_index_container< + sidechain_address_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_unique< tag, + composite_key, + member + > + >, + ordered_unique< tag, + composite_key, + member + > + > + > + >; + using sidechain_address_index = generic_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::sidechain_address_object, (graphene::db::object), + (sidechain_address_account) (sidechain) (deposit_address) (withdraw_address) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_defs.hpp b/libraries/chain/include/graphene/chain/sidechain_defs.hpp new file mode 100644 index 00000000..f51b9eaf --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_defs.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +namespace graphene { namespace chain { + +enum class sidechain_type { + bitcoin, + ethereum, + eos, + peerplays +}; + +} } + +namespace graphene { namespace peerplays_sidechain { + +using sidechain_type = graphene::chain::sidechain_type; + +} } + +FC_REFLECT_ENUM(graphene::chain::sidechain_type, + (bitcoin) + (ethereum) + (eos) + (peerplays) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp new file mode 100644 index 00000000..3bc64bfa --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_evaluator.hpp @@ -0,0 +1,34 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class sidechain_transaction_create_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_create_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_create_operation& o); + object_id_type do_apply(const sidechain_transaction_create_operation& o); +}; + +class sidechain_transaction_sign_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_sign_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_sign_operation& o); + object_id_type do_apply(const sidechain_transaction_sign_operation& o); +}; + +class sidechain_transaction_send_evaluator : public evaluator +{ +public: + typedef sidechain_transaction_send_operation operation_type; + + void_result do_evaluate(const sidechain_transaction_send_operation& o); + object_id_type do_apply(const sidechain_transaction_send_operation& o); +}; + +} } // namespace graphene::chain \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp new file mode 100644 index 00000000..f4f596cf --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_transaction_object.hpp @@ -0,0 +1,69 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class sidechain_transaction_object + * @brief tracks state of sidechain transaction during signing process. + * @ingroup object + */ + class sidechain_transaction_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = sidechain_transaction_object_type; + + sidechain_type sidechain; + object_id_type object_id; + std::string transaction; + std::vector> signers; + + block_id_type block; + bool valid = false; + bool complete = false; + bool sent = false; + }; + + struct by_object_id; + struct by_sidechain_and_complete; + struct by_sidechain_and_complete_and_sent; + using sidechain_transaction_multi_index_type = multi_index_container< + sidechain_transaction_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member + > + >, + ordered_non_unique< tag, + composite_key, + member, + member + > + > + > + >; + using sidechain_transaction_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::sidechain_transaction_object, (graphene::db::object ), + (sidechain) + (object_id) + (transaction) + (signers) + (block) + (valid) + (complete) + (sent) ) diff --git a/libraries/chain/include/graphene/chain/son_evaluator.hpp b/libraries/chain/include/graphene/chain/son_evaluator.hpp index bb6a1820..87554fbc 100644 --- a/libraries/chain/include/graphene/chain/son_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/son_evaluator.hpp @@ -31,4 +31,31 @@ public: void_result do_apply(const son_delete_operation& o); }; +class son_heartbeat_evaluator : public evaluator +{ +public: + typedef son_heartbeat_operation operation_type; + + void_result do_evaluate(const son_heartbeat_operation& o); + object_id_type do_apply(const son_heartbeat_operation& o); +}; + +class son_report_down_evaluator : public evaluator +{ +public: + typedef son_report_down_operation operation_type; + + void_result do_evaluate(const son_report_down_operation& o); + object_id_type do_apply(const son_report_down_operation& o); +}; + +class son_maintenance_evaluator : public evaluator +{ +public: + typedef son_maintenance_operation operation_type; + + void_result do_evaluate(const son_maintenance_operation& o); + object_id_type do_apply(const son_maintenance_operation& o); +}; + } } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_info.hpp b/libraries/chain/include/graphene/chain/son_info.hpp new file mode 100644 index 00000000..bc0c823b --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_info.hpp @@ -0,0 +1,36 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_info + * @brief tracks information about a SON info required to re/create primary wallet + * @ingroup object + */ + struct son_info { + son_id_type son_id; + uint64_t total_votes = 0; + public_key_type signing_key; + flat_map sidechain_public_keys; + + bool operator==(const son_info& rhs) { + bool son_sets_equal = + (son_id == rhs.son_id) && + (total_votes == rhs.total_votes) && + (signing_key == rhs.signing_key) && + (sidechain_public_keys.size() == rhs.sidechain_public_keys.size()); + + if (son_sets_equal) { + // Compare sidechain public keys + } + return son_sets_equal; + } + }; + +} } + +FC_REFLECT( graphene::chain::son_info, + (son_id)(total_votes)(signing_key)(sidechain_public_keys) ) diff --git a/libraries/chain/include/graphene/chain/son_object.hpp b/libraries/chain/include/graphene/chain/son_object.hpp index 77316a4d..5aee1265 100644 --- a/libraries/chain/include/graphene/chain/son_object.hpp +++ b/libraries/chain/include/graphene/chain/son_object.hpp @@ -2,6 +2,7 @@ #include #include #include +#include namespace graphene { namespace chain { using namespace graphene::db; @@ -10,7 +11,9 @@ namespace graphene { namespace chain { { inactive, active, - in_maintenance + request_maintenance, + in_maintenance, + deregistered }; /** * @class son_statistics_object @@ -27,12 +30,24 @@ namespace graphene { namespace chain { static const uint8_t type_id = impl_son_statistics_object_type; son_id_type owner; + // Lifetime total transactions signed + uint64_t total_txs_signed = 0; // Transactions signed since the last son payouts uint64_t txs_signed = 0; + // Total Voted Active time i.e. duration selected as part of voted active SONs + uint64_t total_voted_time = 0; // Total Downtime barring the current down time in seconds, used for stats to present to user uint64_t total_downtime = 0; + // Current Interval Downtime since last maintenance + uint64_t current_interval_downtime = 0; // Down timestamp, if son status is in_maintenance use this fc::time_point_sec last_down_timestamp; + // Last Active heartbeat timestamp + fc::time_point_sec last_active_timestamp; + // Total sidechain transactions reported by SON network while SON was active + uint64_t total_sidechain_txs_reported = 0; + // Sidechain transactions reported by this SON + uint64_t sidechain_txs_reported = 0; }; /** @@ -55,6 +70,7 @@ namespace graphene { namespace chain { vesting_balance_id_type pay_vb; son_statistics_id_type statistics; son_status status = son_status::inactive; + flat_map sidechain_public_keys; void pay_son_fee(share_type pay, database& db); }; @@ -77,23 +93,37 @@ namespace graphene { namespace chain { >; using son_index = generic_index; + struct by_owner; using son_stats_multi_index_type = multi_index_container< son_statistics_object, indexed_by< - ordered_unique< tag, member< object, object_id_type, &object::id > > + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > > >; - using son_stats_index = generic_index; + } } // graphene::chain -FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(in_maintenance) ) +FC_REFLECT_ENUM(graphene::chain::son_status, (inactive)(active)(request_maintenance)(in_maintenance)(deregistered) ) FC_REFLECT_DERIVED( graphene::chain::son_object, (graphene::db::object), - (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb) ) + (son_account)(vote_id)(total_votes)(url)(deposit)(signing_key)(pay_vb)(statistics)(status)(sidechain_public_keys) ) FC_REFLECT_DERIVED( graphene::chain::son_statistics_object, (graphene::db::object), (owner) + (total_txs_signed) (txs_signed) + (total_voted_time) + (total_downtime) + (current_interval_downtime) + (last_down_timestamp) + (last_active_timestamp) + (total_sidechain_txs_reported) + (sidechain_txs_reported) ) diff --git a/libraries/chain/include/graphene/chain/son_proposal_object.hpp b/libraries/chain/include/graphene/chain/son_proposal_object.hpp index a8eb5384..7b1674b8 100644 --- a/libraries/chain/include/graphene/chain/son_proposal_object.hpp +++ b/libraries/chain/include/graphene/chain/son_proposal_object.hpp @@ -8,7 +8,8 @@ namespace graphene { namespace chain { enum class son_proposal_type { - son_deregister_proposal + son_deregister_proposal, + son_report_down_proposal }; class son_proposal_object : public abstract_object @@ -36,6 +37,6 @@ using son_proposal_index = generic_index + +namespace graphene { namespace chain { + +class create_son_wallet_deposit_evaluator : public evaluator +{ +public: + typedef son_wallet_deposit_create_operation operation_type; + + void_result do_evaluate(const son_wallet_deposit_create_operation& o); + object_id_type do_apply(const son_wallet_deposit_create_operation& o); +}; + +class process_son_wallet_deposit_evaluator : public evaluator +{ +public: + typedef son_wallet_deposit_process_operation operation_type; + + void_result do_evaluate(const son_wallet_deposit_process_operation& o); + object_id_type do_apply(const son_wallet_deposit_process_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp new file mode 100644 index 00000000..6ddc44c2 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_deposit_object.hpp @@ -0,0 +1,73 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_deposit_object + * @brief tracks information about a SON wallet deposit. + * @ingroup object + */ + class son_wallet_deposit_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_deposit_object_type; + + time_point_sec timestamp; + sidechain_type sidechain; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + safe sidechain_amount; + chain::account_id_type peerplays_from; + chain::account_id_type peerplays_to; + chain::asset peerplays_asset; + + std::set expected_reports; + std::set received_reports; + + bool processed; + }; + + struct by_sidechain; + struct by_sidechain_uid; + struct by_processed; + struct by_sidechain_and_processed; + using son_wallet_deposit_multi_index_type = multi_index_container< + son_wallet_deposit_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member + > + > + > + >; + using son_wallet_deposit_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_deposit_object, (graphene::db::object), + (timestamp) (sidechain) + (sidechain_uid) (sidechain_transaction_id) (sidechain_from) (sidechain_to) (sidechain_currency) (sidechain_amount) + (peerplays_from) (peerplays_to) (peerplays_asset) + (expected_reports) (received_reports) + (processed) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp b/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp new file mode 100644 index 00000000..78e8655f --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_evaluator.hpp @@ -0,0 +1,25 @@ +#pragma once +#include +#include + +namespace graphene { namespace chain { + +class recreate_son_wallet_evaluator : public evaluator +{ +public: + typedef son_wallet_recreate_operation operation_type; + + void_result do_evaluate(const son_wallet_recreate_operation& o); + object_id_type do_apply(const son_wallet_recreate_operation& o); +}; + +class update_son_wallet_evaluator : public evaluator +{ +public: + typedef son_wallet_update_operation operation_type; + + void_result do_evaluate(const son_wallet_update_operation& o); + object_id_type do_apply(const son_wallet_update_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_object.hpp new file mode 100644 index 00000000..315def33 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_object.hpp @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_object + * @brief tracks information about a SON wallet. + * @ingroup object + */ + class son_wallet_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_object_type; + + time_point_sec valid_from; + time_point_sec expires; + + flat_map addresses; + vector sons; + }; + + struct by_valid_from; + struct by_expires; + using son_wallet_multi_index_type = multi_index_container< + son_wallet_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + > + > + >; + using son_wallet_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_object, (graphene::db::object), + (valid_from) (expires) (addresses) (sons) ) diff --git a/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp b/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp new file mode 100644 index 00000000..f5c08cd3 --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_withdraw_evaluator.hpp @@ -0,0 +1,24 @@ +#pragma once +#include + +namespace graphene { namespace chain { + +class create_son_wallet_withdraw_evaluator : public evaluator +{ +public: + typedef son_wallet_withdraw_create_operation operation_type; + + void_result do_evaluate(const son_wallet_withdraw_create_operation& o); + object_id_type do_apply(const son_wallet_withdraw_create_operation& o); +}; + +class process_son_wallet_withdraw_evaluator : public evaluator +{ +public: + typedef son_wallet_withdraw_process_operation operation_type; + + void_result do_evaluate(const son_wallet_withdraw_process_operation& o); + object_id_type do_apply(const son_wallet_withdraw_process_operation& o); +}; + +} } // namespace graphene::chain diff --git a/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp b/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp new file mode 100644 index 00000000..ef39aadf --- /dev/null +++ b/libraries/chain/include/graphene/chain/son_wallet_withdraw_object.hpp @@ -0,0 +1,72 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + using namespace graphene::db; + + /** + * @class son_wallet_withdraw_object + * @brief tracks information about a SON wallet withdrawal. + * @ingroup object + */ + class son_wallet_withdraw_object : public abstract_object + { + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = son_wallet_withdraw_object_type; + + time_point_sec timestamp; + sidechain_type sidechain; + std::string peerplays_uid; + std::string peerplays_transaction_id; + chain::account_id_type peerplays_from; + chain::asset peerplays_asset; + sidechain_type withdraw_sidechain; + std::string withdraw_address; + std::string withdraw_currency; + safe withdraw_amount; + + std::set expected_reports; + std::set received_reports; + + bool processed; + }; + + struct by_peerplays_uid; + struct by_withdraw_sidechain; + struct by_processed; + struct by_withdraw_sidechain_and_processed; + using son_wallet_withdraw_multi_index_type = multi_index_container< + son_wallet_withdraw_object, + indexed_by< + ordered_unique< tag, + member + >, + ordered_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + member + >, + ordered_non_unique< tag, + composite_key, + member + > + > + > + >; + using son_wallet_withdraw_index = generic_index; +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::son_wallet_withdraw_object, (graphene::db::object), + (timestamp) (sidechain) + (peerplays_uid) (peerplays_transaction_id) (peerplays_from) (peerplays_asset) + (withdraw_sidechain) (withdraw_address) (withdraw_currency) (withdraw_amount) + (expected_reports) (received_reports) + (processed) ) diff --git a/libraries/chain/include/graphene/chain/vote_count.hpp b/libraries/chain/include/graphene/chain/vote_count.hpp index f76a784d..ab2f3612 100644 --- a/libraries/chain/include/graphene/chain/vote_count.hpp +++ b/libraries/chain/include/graphene/chain/vote_count.hpp @@ -63,6 +63,17 @@ struct vote_counter out_auth = auth; } + void finish_2_3( authority& out_auth ) + { + if( total_votes == 0 ) + return; + assert( total_votes <= std::numeric_limits::max() ); + uint32_t weight = uint32_t( total_votes ); + weight = (weight * 2 / 3) + 1; + auth.weight_threshold = weight; + out_auth = auth; + } + bool is_empty()const { return (total_votes == 0); diff --git a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp index e4c4bb51..fc7d6d10 100644 --- a/libraries/chain/include/graphene/chain/witness_schedule_object.hpp +++ b/libraries/chain/include/graphene/chain/witness_schedule_object.hpp @@ -31,6 +31,7 @@ namespace graphene { namespace chain { class witness_schedule_object; +class son_schedule_object; typedef hash_ctr_rng< /* HashClass = */ fc::sha256, @@ -53,6 +54,22 @@ typedef generic_far_future_witness_scheduler< /* debug = */ true > far_future_witness_scheduler; +typedef generic_witness_scheduler< + /* WitnessID = */ son_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_son_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > son_scheduler; + +typedef generic_far_future_witness_scheduler< + /* WitnessID = */ son_id_type, + /* RNG = */ witness_scheduler_rng, + /* CountType = */ decltype( chain_parameters::maximum_son_count ), + /* OffsetType = */ uint32_t, + /* debug = */ true + > far_future_son_scheduler; + class witness_schedule_object : public graphene::db::abstract_object { public: @@ -73,6 +90,26 @@ class witness_schedule_object : public graphene::db::abstract_object +{ + public: + static const uint8_t space_id = implementation_ids; + static const uint8_t type_id = impl_son_schedule_object_type; + + vector< son_id_type > current_shuffled_sons; + + son_scheduler scheduler; + uint32_t last_scheduling_block; + uint64_t slots_since_genesis = 0; + fc::array< char, sizeof(secret_hash_type) > rng_seed; + + /** + * Not necessary for consensus, but used for figuring out the participation rate. + * The nth bit is 0 if the nth slot was unfilled, else it is 1. + */ + fc::uint128 recent_slots_filled; +}; + } } @@ -96,3 +133,23 @@ FC_REFLECT_DERIVED( (recent_slots_filled) (current_shuffled_witnesses) ) +FC_REFLECT( graphene::chain::son_scheduler, + (_turns) + (_tokens) + (_min_token_count) + (_ineligible_waiting_for_token) + (_ineligible_no_turn) + (_eligible) + (_schedule) + (_lame_duck) + ) +FC_REFLECT_DERIVED( + graphene::chain::son_schedule_object, + (graphene::db::object), + (scheduler) + (last_scheduling_block) + (slots_since_genesis) + (rng_seed) + (recent_slots_filled) + (current_shuffled_sons) +) diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index d377e0d8..1a8e2ee2 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -148,6 +148,18 @@ struct proposal_operation_hardfork_visitor FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_delete_operation not allowed yet!" ); } + void operator()(const son_heartbeat_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_heartbeat_operation not allowed yet!" ); + } + + void operator()(const son_report_down_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_report_down_operation not allowed yet!" ); + } + + void operator()(const son_maintenance_operation &v) const { + FC_ASSERT( block_time >= HARDFORK_SON_TIME, "son_maintenance_operation not allowed yet!" ); + } + // loop and self visit in proposals void operator()(const proposal_create_operation &v) const { for (const op_wrapper &op : v.proposed_ops) @@ -164,6 +176,15 @@ void son_hardfork_visitor::operator()( const son_delete_operation &v ) }); } +void son_hardfork_visitor::operator()( const son_report_down_operation &v ) +{ + db.create([&]( son_proposal_object& son_prop ) { + son_prop.proposal_type = son_proposal_type::son_report_down_proposal; + son_prop.proposal_id = prop_id; + son_prop.son_id = v.son_id; + }); +} + void_result proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) { try { const database& d = db(); diff --git a/libraries/chain/protocol/account.cpp b/libraries/chain/protocol/account.cpp index cf592d5c..6721bb07 100644 --- a/libraries/chain/protocol/account.cpp +++ b/libraries/chain/protocol/account.cpp @@ -171,15 +171,22 @@ void account_options::validate() const { auto needed_witnesses = num_witness; auto needed_committee = num_committee; + auto needed_sons = num_son; for( vote_id_type id : votes ) if( id.type() == vote_id_type::witness && needed_witnesses ) --needed_witnesses; else if ( id.type() == vote_id_type::committee && needed_committee ) --needed_committee; + else if ( id.type() == vote_id_type::son && needed_sons ) + --needed_sons; - FC_ASSERT( needed_witnesses == 0 && needed_committee == 0, - "May not specify fewer witnesses or committee members than the number voted for."); + FC_ASSERT( needed_witnesses == 0, + "May not specify fewer witnesses than the number voted for."); + FC_ASSERT( needed_committee == 0, + "May not specify fewer committee members than the number voted for."); + FC_ASSERT( needed_sons == 0, + "May not specify fewer SONs than the number voted for."); } void affiliate_reward_distribution::validate() const diff --git a/libraries/chain/sidechain_address_evaluator.cpp b/libraries/chain/sidechain_address_evaluator.cpp new file mode 100644 index 00000000..5382195d --- /dev/null +++ b/libraries/chain/sidechain_address_evaluator.cpp @@ -0,0 +1,68 @@ +#include + +#include +#include +#include + +namespace graphene { namespace chain { + +void_result add_sidechain_address_evaluator::do_evaluate(const sidechain_address_add_operation& op) +{ try{ + + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(boost::make_tuple(op.sidechain_address_account, op.sidechain)) == idx.end(), "Duplicated item" ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type add_sidechain_address_evaluator::do_apply(const sidechain_address_add_operation& op) +{ try { + const auto& new_sidechain_address_object = db().create( [&]( sidechain_address_object& obj ){ + obj.sidechain_address_account = op.sidechain_address_account; + obj.sidechain = op.sidechain; + obj.deposit_address = op.deposit_address; + obj.withdraw_address = op.withdraw_address; + }); + return new_sidechain_address_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_sidechain_address_evaluator::do_evaluate(const sidechain_address_update_operation& op) +{ try { + FC_ASSERT(db().get(op.sidechain_address_id).sidechain_address_account == op.sidechain_address_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.sidechain_address_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_sidechain_address_evaluator::do_apply(const sidechain_address_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.sidechain_address_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](sidechain_address_object &sao) { + if(op.deposit_address.valid()) sao.deposit_address = *op.deposit_address; + if(op.withdraw_address.valid()) sao.withdraw_address = *op.withdraw_address; + }); + } + return op.sidechain_address_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_sidechain_address_evaluator::do_evaluate(const sidechain_address_delete_operation& op) +{ try { + FC_ASSERT(db().get(op.sidechain_address_id).sidechain_address_account == op.sidechain_address_account); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.sidechain_address_id) != idx.end() ); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result delete_sidechain_address_evaluator::do_apply(const sidechain_address_delete_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto sidechain_address = idx.find(op.sidechain_address_id); + if(sidechain_address != idx.end()) { + db().remove(*sidechain_address); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/sidechain_transaction_evaluator.cpp b/libraries/chain/sidechain_transaction_evaluator.cpp new file mode 100644 index 00000000..3c72b9e9 --- /dev/null +++ b/libraries/chain/sidechain_transaction_evaluator.cpp @@ -0,0 +1,128 @@ +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result sidechain_transaction_create_evaluator::do_evaluate(const sidechain_transaction_create_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + FC_ASSERT((op.object_id.is() || op.object_id.is() || op.object_id.is()), "Invalid object id"); + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.object_id); + FC_ASSERT(sto_obj == sto_idx.end(), "Sidechain transaction for a given object is already created"); + + FC_ASSERT(!op.transaction.empty(), "Sidechain transaction data not set"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_create_evaluator::do_apply(const sidechain_transaction_create_operation &op) +{ try { + const auto &new_sidechain_transaction_object = db().create([&](sidechain_transaction_object &sto) { + sto.sidechain = op.sidechain; + sto.object_id = op.object_id; + sto.transaction = op.transaction; + std::transform(op.signers.begin(), op.signers.end(), std::inserter(sto.signers, sto.signers.end()), [](const son_id_type son_id) { + return std::make_pair(son_id, false); + }); + sto.block = db().head_block_id(); + sto.valid = true; + sto.complete = false; + sto.sent = false; + }); + return new_sidechain_transaction_object.id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +void_result sidechain_transaction_sign_evaluator::do_evaluate(const sidechain_transaction_sign_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.sidechain_transaction_id); + FC_ASSERT(sto_obj != sto_idx.end(), "Sidechain transaction object not found"); + + const auto &son_idx = db().get_index_type().indices().get(); + const auto &son_obj = son_idx.find(op.payer); + FC_ASSERT(son_obj != son_idx.end(), "SON object not found"); + + bool expected = false; + for (auto signer : sto_obj->signers) { + if (signer.first == son_obj->id) { + expected = !signer.second; + } + } + FC_ASSERT(expected, "Signer not expected"); + + FC_ASSERT(sto_obj->block == op.block, "Sidechain transaction already signed in this block"); + + FC_ASSERT(sto_obj->valid, "Transaction not valid"); + FC_ASSERT(!sto_obj->complete, "Transaction signing completed"); + FC_ASSERT(!sto_obj->sent, "Transaction already sent"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_sign_evaluator::do_apply(const sidechain_transaction_sign_operation &op) +{ try { + const auto &sto_idx = db().get_index_type().indices().get(); + auto sto_obj = sto_idx.find(op.sidechain_transaction_id); + + const auto &son_idx = db().get_index_type().indices().get(); + auto son_obj = son_idx.find(op.payer); + + db().modify(*sto_obj, [&](sidechain_transaction_object &sto) { + sto.transaction = op.transaction; + sto.block = db().head_block_id(); + sto.complete = op.complete; + for (size_t i = 0; i < sto.signers.size(); i++) { + if (sto.signers.at(i).first == son_obj->id) { + sto.signers.at(i).second = true; + } + } + }); + + db().modify(son_obj->statistics(db()), [&](son_statistics_object& sso) { + sso.txs_signed += 1; + }); + + return op.sidechain_transaction_id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +void_result sidechain_transaction_send_evaluator::do_evaluate(const sidechain_transaction_send_operation &op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + + const auto &sto_idx = db().get_index_type().indices().get(); + const auto &sto_obj = sto_idx.find(op.sidechain_transaction_id); + FC_ASSERT(sto_obj != sto_idx.end(), "Sidechain transaction object not found"); + + FC_ASSERT(sto_obj->valid, "Transaction not valid"); + FC_ASSERT(sto_obj->complete, "Transaction signing not complete"); + FC_ASSERT(!sto_obj->sent, "Transaction already sent"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +object_id_type sidechain_transaction_send_evaluator::do_apply(const sidechain_transaction_send_operation &op) +{ try { + const auto &sto_idx = db().get_index_type().indices().get(); + auto sto_obj = sto_idx.find(op.sidechain_transaction_id); + + db().modify(*sto_obj, [&](sidechain_transaction_object &sto) { + sto.block = db().head_block_id(); + sto.sent = true; + }); + + return op.sidechain_transaction_id; +} FC_CAPTURE_AND_RETHROW( ( op ) ) } + +} // namespace chain +} // namespace graphene diff --git a/libraries/chain/son_evaluator.cpp b/libraries/chain/son_evaluator.cpp index c54d1391..021d8d20 100644 --- a/libraries/chain/son_evaluator.cpp +++ b/libraries/chain/son_evaluator.cpp @@ -30,6 +30,7 @@ object_id_type create_son_evaluator::do_apply(const son_create_operation& op) obj.url = op.url; obj.deposit = op.deposit; obj.signing_key = op.signing_key; + obj.sidechain_public_keys = op.sidechain_public_keys; obj.pay_vb = op.pay_vb; obj.statistics = db().create([&](son_statistics_object& s){s.owner = obj.id;}).id; }); @@ -55,6 +56,7 @@ object_id_type update_son_evaluator::do_apply(const son_update_operation& op) if(op.new_url.valid()) so.url = *op.new_url; if(op.new_deposit.valid()) so.deposit = *op.new_deposit; if(op.new_signing_key.valid()) so.signing_key = *op.new_signing_key; + if(op.new_sidechain_public_keys.valid()) so.sidechain_public_keys = *op.new_sidechain_public_keys; if(op.new_pay_vb.valid()) so.pay_vb = *op.new_pay_vb; }); } @@ -64,11 +66,8 @@ object_id_type update_son_evaluator::do_apply(const son_update_operation& op) void_result delete_son_evaluator::do_evaluate(const son_delete_operation& op) { try { FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON_HARDFORK"); // can be removed after HF date pass - // Get the current block witness signatory - witness_id_type wit_id = db().get_scheduled_witness(1); - const witness_object& current_witness = wit_id(db()); - // Either owner can remove or witness - FC_ASSERT(db().get(op.son_id).son_account == op.owner_account || (db().is_son_dereg_valid(op.son_id) && op.payer == current_witness.witness_account)); + // Either owner can remove or consensus son account + FC_ASSERT(op.payer == db().get(op.son_id).son_account || (db().is_son_dereg_valid(op.son_id) && op.payer == db().get_global_properties().parameters.son_account())); const auto& idx = db().get_index_type().indices().get(); FC_ASSERT( idx.find(op.son_id) != idx.end() ); return void_result(); @@ -83,6 +82,7 @@ void_result delete_son_evaluator::do_apply(const son_delete_operation& op) linear_vesting_policy new_vesting_policy; new_vesting_policy.begin_timestamp = db().head_block_time(); new_vesting_policy.vesting_cliff_seconds = db().get_global_properties().parameters.son_vesting_period(); + new_vesting_policy.begin_balance = deposit.balance.amount; db().modify(son->deposit(db()), [&new_vesting_policy](vesting_balance_object &vbo) { vbo.policy = new_vesting_policy; @@ -93,4 +93,145 @@ void_result delete_son_evaluator::do_apply(const son_delete_operation& op) return void_result(); } FC_CAPTURE_AND_RETHROW( (op) ) } +void_result son_heartbeat_evaluator::do_evaluate(const son_heartbeat_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + FC_ASSERT( itr != idx.end() ); + FC_ASSERT(itr->son_account == op.owner_account); + auto stats = itr->statistics( db() ); + // Inactive SONs need not send heartbeats + FC_ASSERT((itr->status == son_status::active) || (itr->status == son_status::in_maintenance) || (itr->status == son_status::request_maintenance), "Inactive SONs need not send heartbeats"); + // Account for network delays + fc::time_point_sec min_ts = db().head_block_time() - fc::seconds(5 * db().block_interval()); + // Account for server ntp sync difference + fc::time_point_sec max_ts = db().head_block_time() + fc::seconds(5 * db().block_interval()); + FC_ASSERT(op.ts > stats.last_active_timestamp, "Heartbeat sent without waiting minimum time"); + FC_ASSERT(op.ts > stats.last_down_timestamp, "Heartbeat sent is invalid can't be <= last down timestamp"); + FC_ASSERT(op.ts >= min_ts, "Heartbeat ts is behind the min threshold"); + FC_ASSERT(op.ts <= max_ts, "Heartbeat ts is above the max threshold"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_heartbeat_evaluator::do_apply(const son_heartbeat_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + const global_property_object& gpo = db().get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + + auto it_son = std::find(active_son_ids.begin(), active_son_ids.end(), op.son_id); + bool is_son_active = true; + + if(it_son == active_son_ids.end()) { + is_son_active = false; + } + + if(itr->status == son_status::in_maintenance) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.current_interval_downtime += op.ts.sec_since_epoch() - sso.last_down_timestamp.sec_since_epoch(); + sso.last_active_timestamp = op.ts; + // TODO: Remove me after sidechain tx signing is finished + sso.txs_signed = sso.txs_signed + 1; + } ); + + db().modify(*itr, [&is_son_active](son_object &so) { + if(is_son_active) { + so.status = son_status::active; + } else { + so.status = son_status::inactive; + } + }); + } else if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.last_active_timestamp = op.ts; + // TODO: Remove me after sidechain tx signing is finished + sso.txs_signed = sso.txs_signed + 1; + } ); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result son_report_down_evaluator::do_evaluate(const son_report_down_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer."); + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_id) != idx.end() ); + auto itr = idx.find(op.son_id); + auto stats = itr->statistics( db() ); + FC_ASSERT(itr->status == son_status::active || itr->status == son_status::request_maintenance, "Inactive/Deregistered/in_maintenance SONs cannot be reported on as down"); + FC_ASSERT(op.down_ts >= stats.last_active_timestamp, "down_ts should be greater than last_active_timestamp"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_report_down_evaluator::do_apply(const son_report_down_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + if ((itr->status == son_status::active) || (itr->status == son_status::request_maintenance)) { + db().modify( itr->statistics( db() ), [&]( son_statistics_object& sso ) + { + sso.last_down_timestamp = op.down_ts; + }); + + db().modify(*itr, [&op](son_object &so) { + so.status = son_status::in_maintenance; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result son_maintenance_evaluator::do_evaluate(const son_maintenance_operation& op) +{ try { + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass + FC_ASSERT(db().get(op.son_id).son_account == op.owner_account); + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + FC_ASSERT( itr != idx.end() ); + // Inactive SONs can't go to maintenance, toggle between active and request_maintenance states + if(op.request_type == son_maintenance_request_type::request_maintenance) { + FC_ASSERT(itr->status == son_status::active, "Inactive SONs can't request for maintenance"); + } else if(op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + FC_ASSERT(itr->status == son_status::request_maintenance, "Only maintenance requested SONs can cancel the request"); + } else { + FC_ASSERT(false, "Invalid maintenance operation"); + } + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type son_maintenance_evaluator::do_apply(const son_maintenance_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_id); + if(itr != idx.end()) + { + if(itr->status == son_status::active && op.request_type == son_maintenance_request_type::request_maintenance) { + db().modify(*itr, [](son_object &so) { + so.status = son_status::request_maintenance; + }); + } else if(itr->status == son_status::request_maintenance && op.request_type == son_maintenance_request_type::cancel_request_maintenance) { + db().modify(*itr, [](son_object &so) { + so.status = son_status::active; + }); + } + } + return op.son_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + } } // namespace graphene::chain diff --git a/libraries/chain/son_wallet_deposit_evaluator.cpp b/libraries/chain/son_wallet_deposit_evaluator.cpp new file mode 100644 index 00000000..e2734ea4 --- /dev/null +++ b/libraries/chain/son_wallet_deposit_evaluator.cpp @@ -0,0 +1,94 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_deposit_create_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto &idx = db().get_index_type().indices().get(); + FC_ASSERT(idx.find(op.son_id) != idx.end(), "Statistic object for a given SON ID does not exists"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_wallet_deposit_evaluator::do_apply(const son_wallet_deposit_create_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.sidechain_uid); + if (itr == idx.end()) { + const auto& new_son_wallet_deposit_object = db().create( [&]( son_wallet_deposit_object& swdo ){ + swdo.timestamp = op.timestamp; + swdo.sidechain = op.sidechain; + swdo.sidechain_uid = op.sidechain_uid; + swdo.sidechain_transaction_id = op.sidechain_transaction_id; + swdo.sidechain_from = op.sidechain_from; + swdo.sidechain_to = op.sidechain_to; + swdo.sidechain_currency = op.sidechain_currency; + swdo.sidechain_amount = op.sidechain_amount; + swdo.peerplays_from = op.peerplays_from; + swdo.peerplays_to = op.peerplays_to; + swdo.peerplays_asset = op.peerplays_asset; + + auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons) { + swdo.expected_reports.insert(si.son_id); + + auto stats_itr = db().get_index_type().indices().get().find(si.son_id); + db().modify(*stats_itr, [&op, &si](son_statistics_object &sso) { + sso.total_sidechain_txs_reported = sso.total_sidechain_txs_reported + 1; + if (si.son_id == op.son_id) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + } + }); + } + + swdo.received_reports.insert(op.son_id); + + swdo.processed = false; + }); + return new_son_wallet_deposit_object.id; + } else { + db().modify(*itr, [&op](son_wallet_deposit_object &swdo) { + swdo.received_reports.insert(op.son_id); + }); + auto stats_itr = db().get_index_type().indices().get().find(op.son_id); + db().modify(*stats_itr, [&op](son_statistics_object &sso) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + }); + return (*itr).id; + } +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result process_son_wallet_deposit_evaluator::do_evaluate(const son_wallet_deposit_process_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + const auto& itr = idx.find(op.son_wallet_deposit_id); + FC_ASSERT(itr != idx.end(), "Son wallet deposit not found"); + FC_ASSERT(!itr->processed, "Son wallet deposit is already processed"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type process_son_wallet_deposit_evaluator::do_apply(const son_wallet_deposit_process_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_deposit_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_wallet_deposit_object &swdo) { + swdo.processed = true; + }); + } + return op.son_wallet_deposit_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/son_wallet_evaluator.cpp b/libraries/chain/son_wallet_evaluator.cpp new file mode 100644 index 00000000..0baed1cb --- /dev/null +++ b/libraries/chain/son_wallet_evaluator.cpp @@ -0,0 +1,81 @@ +#include + +#include +#include + +namespace graphene { namespace chain { + +void_result recreate_son_wallet_evaluator::do_evaluate(const son_wallet_recreate_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.rbegin(); + if(itr != idx.rend()) + { + // Compare current wallet SONs and to-be lists of active sons + auto cur_wallet_sons = (*itr).sons; + auto new_wallet_sons = op.sons; + + bool son_sets_equal = (cur_wallet_sons.size() == new_wallet_sons.size()); + if (son_sets_equal) { + for( size_t i = 0; i < cur_wallet_sons.size(); i++ ) { + son_sets_equal = son_sets_equal && cur_wallet_sons.at(i) == new_wallet_sons.at(i); + } + } + + FC_ASSERT(son_sets_equal == false, "Wallet recreation not needed, active SONs set is not changed."); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type recreate_son_wallet_evaluator::do_apply(const son_wallet_recreate_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.rbegin(); + if(itr != idx.rend()) + { + db().modify(*itr, [&, op](son_wallet_object &swo) { + swo.expires = db().head_block_time(); + }); + } + + const auto& new_son_wallet_object = db().create( [&]( son_wallet_object& obj ){ + obj.valid_from = db().head_block_time(); + obj.expires = time_point_sec::maximum(); + obj.sons = op.sons; + }); + return new_son_wallet_object.id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result update_son_wallet_evaluator::do_evaluate(const son_wallet_update_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + FC_ASSERT( idx.find(op.son_wallet_id) != idx.end() ); + //auto itr = idx.find(op.son_wallet_id); + //FC_ASSERT( itr->addresses.find(op.sidechain) == itr->addresses.end() || + // itr->addresses.at(op.sidechain).empty(), "Sidechain wallet address already set"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type update_son_wallet_evaluator::do_apply(const son_wallet_update_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_id); + if (itr != idx.end()) + { + if (itr->addresses.find(op.sidechain) == itr->addresses.end()) { + db().modify(*itr, [&op](son_wallet_object &swo) { + swo.addresses[op.sidechain] = op.address; + }); + } + } + return op.son_wallet_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/chain/son_wallet_withdraw_evaluator.cpp b/libraries/chain/son_wallet_withdraw_evaluator.cpp new file mode 100644 index 00000000..7a3930b1 --- /dev/null +++ b/libraries/chain/son_wallet_withdraw_evaluator.cpp @@ -0,0 +1,93 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result create_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_create_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto &idx = db().get_index_type().indices().get(); + FC_ASSERT(idx.find(op.son_id) != idx.end(), "Statistic object for a given SON ID does not exists"); + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type create_son_wallet_withdraw_evaluator::do_apply(const son_wallet_withdraw_create_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.peerplays_uid); + if (itr == idx.end()) { + const auto& new_son_wallet_withdraw_object = db().create( [&]( son_wallet_withdraw_object& swwo ){ + swwo.timestamp = op.timestamp; + swwo.sidechain = op.sidechain; + swwo.peerplays_uid = op.peerplays_uid; + swwo.peerplays_transaction_id = op.peerplays_transaction_id; + swwo.peerplays_from = op.peerplays_from; + swwo.peerplays_asset = op.peerplays_asset; + swwo.withdraw_sidechain = op.withdraw_sidechain; + swwo.withdraw_address = op.withdraw_address; + swwo.withdraw_currency = op.withdraw_currency; + swwo.withdraw_amount = op.withdraw_amount; + + auto &gpo = db().get_global_properties(); + for (auto &si : gpo.active_sons) { + swwo.expected_reports.insert(si.son_id); + + auto stats_itr = db().get_index_type().indices().get().find(si.son_id); + db().modify(*stats_itr, [&op, &si](son_statistics_object &sso) { + sso.total_sidechain_txs_reported = sso.total_sidechain_txs_reported + 1; + if (si.son_id == op.son_id) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + } + }); + } + + swwo.received_reports.insert(op.son_id); + + swwo.processed = false; + }); + return new_son_wallet_withdraw_object.id; + } else { + db().modify(*itr, [&op](son_wallet_withdraw_object &swwo) { + swwo.received_reports.insert(op.son_id); + }); + auto stats_itr = db().get_index_type().indices().get().find(op.son_id); + db().modify(*stats_itr, [&op](son_statistics_object &sso) { + sso.sidechain_txs_reported = sso.sidechain_txs_reported + 1; + }); + return (*itr).id; + } +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result process_son_wallet_withdraw_evaluator::do_evaluate(const son_wallet_withdraw_process_operation& op) +{ try{ + FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); + FC_ASSERT( op.payer == db().get_global_properties().parameters.son_account(), "SON paying account must be set as payer." ); + + const auto& idx = db().get_index_type().indices().get(); + const auto& itr = idx.find(op.son_wallet_withdraw_id); + FC_ASSERT(itr != idx.end(), "Son wallet withdraw not found"); + FC_ASSERT(!itr->processed, "Son wallet withdraw is already processed"); + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +object_id_type process_son_wallet_withdraw_evaluator::do_apply(const son_wallet_withdraw_process_operation& op) +{ try { + const auto& idx = db().get_index_type().indices().get(); + auto itr = idx.find(op.son_wallet_withdraw_id); + if(itr != idx.end()) + { + db().modify(*itr, [&op](son_wallet_withdraw_object &swwo) { + swwo.processed = true; + }); + } + return op.son_wallet_withdraw_id; +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } // namespace graphene::chain diff --git a/libraries/db/include/graphene/db/index.hpp b/libraries/db/include/graphene/db/index.hpp index e8d4fa11..1bc593f4 100644 --- a/libraries/db/include/graphene/db/index.hpp +++ b/libraries/db/include/graphene/db/index.hpp @@ -402,15 +402,6 @@ namespace graphene { namespace db { DerivedIndex::remove(obj); } - virtual const object& insert( object&& obj )override - { - const auto& res = DerivedIndex::insert(std::move(obj)); - for( const auto& item : _sindex ) - item->object_inserted( res ); - on_add(res); - return res; - } - virtual void modify( const object& obj, const std::function& m )override { save_undo( obj ); diff --git a/libraries/plugins/peerplays_sidechain/CMakeLists.txt b/libraries/plugins/peerplays_sidechain/CMakeLists.txt old mode 100644 new mode 100755 index 931d4f45..e7d9acfe --- a/libraries/plugins/peerplays_sidechain/CMakeLists.txt +++ b/libraries/plugins/peerplays_sidechain/CMakeLists.txt @@ -2,9 +2,21 @@ file(GLOB HEADERS "include/graphene/peerplays_sidechain/*.hpp") add_library( peerplays_sidechain peerplays_sidechain_plugin.cpp + sidechain_net_manager.cpp + sidechain_net_handler.cpp + sidechain_net_handler_bitcoin.cpp + sidechain_net_handler_peerplays.cpp + bitcoin_utils.cpp ) -target_link_libraries( peerplays_sidechain graphene_chain graphene_app ) +if (SUPPORT_MULTIPLE_SONS) + message ("Multiple SONs per software instance are supported") + target_compile_definitions(peerplays_sidechain PRIVATE SUPPORT_MULTIPLE_SONS) +endif() +unset(SUPPORT_MULTIPLE_SONS) +unset(SUPPORT_MULTIPLE_SONS CACHE) + +target_link_libraries( peerplays_sidechain graphene_chain graphene_app fc zmq ) target_include_directories( peerplays_sidechain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp new file mode 100644 index 00000000..a11647de --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin_utils.cpp @@ -0,0 +1,680 @@ +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +static const unsigned char OP_0 = 0x00; +static const unsigned char OP_1 = 0x51; +static const unsigned char OP_2 = 0x52; +static const unsigned char OP_3 = 0x53; +static const unsigned char OP_4 = 0x54; +static const unsigned char OP_5 = 0x55; +static const unsigned char OP_6 = 0x56; +static const unsigned char OP_7 = 0x57; +static const unsigned char OP_8 = 0x58; +static const unsigned char OP_9 = 0x59; +static const unsigned char OP_10 = 0x5a; +static const unsigned char OP_11 = 0x5b; +static const unsigned char OP_12 = 0x5c; +static const unsigned char OP_13 = 0x5d; +static const unsigned char OP_14 = 0x5e; +static const unsigned char OP_15 = 0x5f; +static const unsigned char OP_16 = 0x60; + +static const unsigned char OP_IF = 0x63; +static const unsigned char OP_ENDIF = 0x68; +static const unsigned char OP_SWAP = 0x7c; +static const unsigned char OP_EQUAL = 0x87; +static const unsigned char OP_ADD = 0x93; +static const unsigned char OP_GREATERTHAN = 0xa0; +static const unsigned char OP_HASH160 = 0xa9; +static const unsigned char OP_CHECKSIG = 0xac; + +class WriteBytesStream { +public: + WriteBytesStream(bytes &buffer) : + storage_(buffer) { + } + + void write(const unsigned char *d, size_t s) { + storage_.insert(storage_.end(), d, d + s); + } + + bool put(unsigned char c) { + storage_.push_back(c); + return true; + } + + void writedata8(uint8_t obj) { + write((unsigned char *)&obj, 1); + } + + void writedata16(uint16_t obj) { + obj = htole16(obj); + write((unsigned char *)&obj, 2); + } + + void writedata32(uint32_t obj) { + obj = htole32(obj); + write((unsigned char *)&obj, 4); + } + + void writedata64(uint64_t obj) { + obj = htole64(obj); + write((unsigned char *)&obj, 8); + } + + void write_compact_int(uint64_t val) { + if (val < 253) { + writedata8(val); + } else if (val <= std::numeric_limits::max()) { + writedata8(253); + writedata16(val); + } else if (val <= std::numeric_limits::max()) { + writedata8(254); + writedata32(val); + } else { + writedata8(255); + writedata64(val); + } + } + + void writedata(const bytes &data) { + write_compact_int(data.size()); + write(&data[0], data.size()); + } + +private: + bytes &storage_; +}; + +class ReadBytesStream { +public: + ReadBytesStream(const bytes &buffer, size_t pos = 0) : + storage_(buffer), + pos_(pos), + end_(buffer.size()) { + } + + size_t current_pos() const { + return pos_; + } + void set_pos(size_t pos) { + if (pos > end_) + FC_THROW("Invalid position in BTC tx buffer"); + pos_ = pos; + } + + inline bool read(unsigned char *d, size_t s) { + if (end_ - pos_ >= s) { + memcpy(d, &storage_[pos_], s); + pos_ += s; + return true; + } + FC_THROW("invalid bitcoin tx buffer"); + } + + inline bool get(unsigned char &c) { + if (pos_ < end_) { + c = storage_[pos_++]; + return true; + } + FC_THROW("invalid bitcoin tx buffer"); + } + + uint8_t readdata8() { + uint8_t obj; + read((unsigned char *)&obj, 1); + return obj; + } + uint16_t readdata16() { + uint16_t obj; + read((unsigned char *)&obj, 2); + return le16toh(obj); + } + uint32_t readdata32() { + uint32_t obj; + read((unsigned char *)&obj, 4); + return le32toh(obj); + } + uint64_t readdata64() { + uint64_t obj; + read((unsigned char *)&obj, 8); + return le64toh(obj); + } + + uint64_t read_compact_int() { + uint8_t size = readdata8(); + uint64_t ret = 0; + if (size < 253) { + ret = size; + } else if (size == 253) { + ret = readdata16(); + if (ret < 253) + FC_THROW("non-canonical ReadCompactSize()"); + } else if (size == 254) { + ret = readdata32(); + if (ret < 0x10000u) + FC_THROW("non-canonical ReadCompactSize()"); + } else { + ret = readdata64(); + if (ret < 0x100000000ULL) + FC_THROW("non-canonical ReadCompactSize()"); + } + if (ret > (uint64_t)0x02000000) + FC_THROW("ReadCompactSize(): size too large"); + return ret; + } + + void readdata(bytes &data) { + size_t s = read_compact_int(); + data.clear(); + data.resize(s); + read(&data[0], s); + } + +private: + const bytes &storage_; + size_t pos_; + size_t end_; +}; + +void btc_outpoint::to_bytes(bytes &stream) const { + WriteBytesStream str(stream); + // TODO: write size? + str.write((unsigned char *)hash.data(), hash.data_size()); + str.writedata32(n); +} + +size_t btc_outpoint::fill_from_bytes(const bytes &data, size_t pos) { + ReadBytesStream str(data, pos); + // TODO: read size? + str.read((unsigned char *)hash.data(), hash.data_size()); + n = str.readdata32(); + return str.current_pos(); +} + +void btc_in::to_bytes(bytes &stream) const { + prevout.to_bytes(stream); + WriteBytesStream str(stream); + str.writedata(scriptSig); + str.writedata32(nSequence); +} + +size_t btc_in::fill_from_bytes(const bytes &data, size_t pos) { + pos = prevout.fill_from_bytes(data, pos); + ReadBytesStream str(data, pos); + str.readdata(scriptSig); + nSequence = str.readdata32(); + return str.current_pos(); +} + +void btc_out::to_bytes(bytes &stream) const { + WriteBytesStream str(stream); + str.writedata64(nValue); + str.writedata(scriptPubKey); +} + +size_t btc_out::fill_from_bytes(const bytes &data, size_t pos) { + ReadBytesStream str(data, pos); + nValue = str.readdata64(); + str.readdata(scriptPubKey); + return str.current_pos(); +} + +void btc_tx::to_bytes(bytes &stream) const { + WriteBytesStream str(stream); + str.writedata32(nVersion); + if (hasWitness) { + // write dummy inputs and flag + str.write_compact_int(0); + unsigned char flags = 1; + str.put(flags); + } + str.write_compact_int(vin.size()); + for (const auto &in : vin) + in.to_bytes(stream); + str.write_compact_int(vout.size()); + for (const auto &out : vout) + out.to_bytes(stream); + if (hasWitness) { + for (const auto &in : vin) { + str.write_compact_int(in.scriptWitness.size()); + for (const auto &stack_item : in.scriptWitness) + str.writedata(stack_item); + } + } + str.writedata32(nLockTime); +} + +size_t btc_tx::fill_from_bytes(const bytes &data, size_t pos) { + ReadBytesStream ds(data, pos); + nVersion = ds.readdata32(); + unsigned char flags = 0; + vin.clear(); + vout.clear(); + hasWitness = false; + /* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */ + size_t vin_size = ds.read_compact_int(); + vin.resize(vin_size); + pos = ds.current_pos(); + for (auto &in : vin) + pos = in.fill_from_bytes(data, pos); + ds.set_pos(pos); + if (vin_size == 0) { + /* We read a dummy or an empty vin. */ + ds.get(flags); + if (flags != 0) { + size_t vin_size = ds.read_compact_int(); + vin.resize(vin_size); + pos = ds.current_pos(); + for (auto &in : vin) + pos = in.fill_from_bytes(data, pos); + ds.set_pos(pos); + size_t vout_size = ds.read_compact_int(); + vout.resize(vout_size); + pos = ds.current_pos(); + for (auto &out : vout) + pos = out.fill_from_bytes(data, pos); + ds.set_pos(pos); + hasWitness = true; + } + } else { + /* We read a non-empty vin. Assume a normal vout follows. */ + size_t vout_size = ds.read_compact_int(); + vout.resize(vout_size); + pos = ds.current_pos(); + for (auto &out : vout) + pos = out.fill_from_bytes(data, pos); + ds.set_pos(pos); + } + if (hasWitness) { + /* The witness flag is present, and we support witnesses. */ + for (auto &in : vin) { + unsigned int size = ds.read_compact_int(); + in.scriptWitness.resize(size); + for (auto &stack_item : in.scriptWitness) + ds.readdata(stack_item); + } + } + nLockTime = ds.readdata32(); + return ds.current_pos(); +} + +void add_data_to_script(bytes &script, const bytes &data) { + WriteBytesStream str(script); + str.writedata(data); +} + +void add_number_to_script(bytes &script, unsigned char data) { + WriteBytesStream str(script); + if (data == 0) + str.put(OP_0); + else if (data == 1) + str.put(OP_1); + else if (data == 2) + str.put(OP_2); + else if (data == 3) + str.put(OP_3); + else if (data == 4) + str.put(OP_4); + else if (data == 5) + str.put(OP_5); + else if (data == 6) + str.put(OP_6); + else if (data == 7) + str.put(OP_7); + else if (data == 8) + str.put(OP_8); + else if (data == 9) + str.put(OP_9); + else if (data == 10) + str.put(OP_10); + else if (data == 11) + str.put(OP_11); + else if (data == 12) + str.put(OP_12); + else if (data == 13) + str.put(OP_13); + else if (data == 14) + str.put(OP_14); + else if (data == 15) + str.put(OP_15); + else if (data == 16) + str.put(OP_16); + else + add_data_to_script(script, {data}); +} + +bytes generate_redeem_script(std::vector> key_data) { + int total_weight = 0; + bytes result; + add_number_to_script(result, 0); + for (auto &p : key_data) { + total_weight += p.second; + result.push_back(OP_SWAP); + auto raw_data = p.first.serialize(); + add_data_to_script(result, bytes(raw_data.begin(), raw_data.begin() + raw_data.size())); + result.push_back(OP_CHECKSIG); + result.push_back(OP_IF); + add_number_to_script(result, static_cast(p.second)); + result.push_back(OP_ADD); + result.push_back(OP_ENDIF); + } + int threshold_weight = 2 * total_weight / 3; + add_number_to_script(result, static_cast(threshold_weight)); + result.push_back(OP_GREATERTHAN); + return result; +} + +/** The Bech32 character set for encoding. */ +const char *charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** Concatenate two byte arrays. */ +bytes cat(bytes x, const bytes &y) { + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** Expand a HRP for use in checksum computation. */ +bytes expand_hrp(const std::string &hrp) { + bytes ret; + ret.resize(hrp.size() * 2 + 1); + for (size_t i = 0; i < hrp.size(); ++i) { + unsigned char c = hrp[i]; + ret[i] = c >> 5; + ret[i + hrp.size() + 1] = c & 0x1f; + } + ret[hrp.size()] = 0; + return ret; +} + +/** Find the polynomial with value coefficients mod the generator as 30-bit. */ +uint32_t polymod(const bytes &values) { + uint32_t chk = 1; + for (size_t i = 0; i < values.size(); ++i) { + uint8_t top = chk >> 25; + chk = (chk & 0x1ffffff) << 5 ^ values[i] ^ + (-((top >> 0) & 1) & 0x3b6a57b2UL) ^ + (-((top >> 1) & 1) & 0x26508e6dUL) ^ + (-((top >> 2) & 1) & 0x1ea119faUL) ^ + (-((top >> 3) & 1) & 0x3d4233ddUL) ^ + (-((top >> 4) & 1) & 0x2a1462b3UL); + } + return chk; +} + +/** Create a checksum. */ +bytes bech32_checksum(const std::string &hrp, const bytes &values) { + bytes enc = cat(expand_hrp(hrp), values); + enc.resize(enc.size() + 6); + uint32_t mod = polymod(enc) ^ 1; + bytes ret; + ret.resize(6); + for (size_t i = 0; i < 6; ++i) { + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +/** Encode a Bech32 string. */ +std::string bech32(const std::string &hrp, const bytes &values) { + bytes checksum = bech32_checksum(hrp, values); + bytes combined = cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (size_t i = 0; i < combined.size(); ++i) { + ret += charset[combined[i]]; + } + return ret; +} + +/** Convert from one power-of-2 number base to another. */ +template +bool convertbits(bytes &out, const bytes &in) { + int acc = 0; + int bits = 0; + const int maxv = (1 << tobits) - 1; + const int max_acc = (1 << (frombits + tobits - 1)) - 1; + for (size_t i = 0; i < in.size(); ++i) { + int value = in[i]; + acc = ((acc << frombits) | value) & max_acc; + bits += frombits; + while (bits >= tobits) { + bits -= tobits; + out.push_back((acc >> bits) & maxv); + } + } + if (pad) { + if (bits) + out.push_back((acc << (tobits - bits)) & maxv); + } else if (bits >= frombits || ((acc << (tobits - bits)) & maxv)) { + return false; + } + return true; +} + +/** Encode a SegWit address. */ +std::string segwit_addr_encode(const std::string &hrp, uint8_t witver, const bytes &witprog) { + bytes enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = bech32(hrp, enc); + return ret; +} + +std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network) { + // calc script hash + fc::sha256 sh = fc::sha256::hash(reinterpret_cast(&script[0]), script.size()); + bytes wp(sh.data(), sh.data() + sh.data_size()); + switch (network) { + case (mainnet): + return segwit_addr_encode("bc", 0, wp); + case (testnet): + case (regtest): + return segwit_addr_encode("tb", 0, wp); + default: + FC_THROW("Unknown bitcoin network type"); + } + FC_THROW("Unknown bitcoin network type"); +} + +bytes lock_script_for_redeem_script(const bytes &script) { + bytes result; + result.push_back(OP_0); + fc::sha256 h = fc::sha256::hash(reinterpret_cast(&script[0]), script.size()); + bytes shash(h.data(), h.data() + h.data_size()); + add_data_to_script(result, shash); + return result; +} + +bytes hash_prevouts(const btc_tx &unsigned_tx) { + fc::sha256::encoder hasher; + for (const auto &in : unsigned_tx.vin) { + bytes data; + in.prevout.to_bytes(data); + hasher.write(reinterpret_cast(&data[0]), data.size()); + } + fc::sha256 res = fc::sha256::hash(hasher.result()); + return bytes(res.data(), res.data() + res.data_size()); +} + +bytes hash_sequence(const btc_tx &unsigned_tx) { + fc::sha256::encoder hasher; + for (const auto &in : unsigned_tx.vin) { + hasher.write(reinterpret_cast(&in.nSequence), sizeof(in.nSequence)); + } + fc::sha256 res = fc::sha256::hash(hasher.result()); + return bytes(res.data(), res.data() + res.data_size()); +} + +bytes hash_outputs(const btc_tx &unsigned_tx) { + fc::sha256::encoder hasher; + for (const auto &out : unsigned_tx.vout) { + bytes data; + out.to_bytes(data); + hasher.write(reinterpret_cast(&data[0]), data.size()); + } + fc::sha256 res = fc::sha256::hash(hasher.result()); + return bytes(res.data(), res.data() + res.data_size()); +} + +const secp256k1_context_t *btc_get_context() { + static secp256k1_context_t *ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN); + return ctx; +} + +bytes der_sign(const fc::ecc::private_key &priv_key, const fc::sha256 &digest) { + fc::ecc::signature result; + int size = result.size(); + FC_ASSERT(secp256k1_ecdsa_sign(btc_get_context(), + (unsigned char *)digest.data(), + (unsigned char *)result.begin(), + &size, + (unsigned char *)priv_key.get_secret().data(), + secp256k1_nonce_function_rfc6979, + nullptr)); + return bytes(result.begin(), result.begin() + size); +} + +std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, + std::vector in_amounts, + const bytes &redeem_script, + const fc::ecc::private_key &priv_key) { + btc_tx tx; + tx.fill_from_bytes(unsigned_tx); + + FC_ASSERT(tx.vin.size() == in_amounts.size(), "Incorrect input amounts data"); + + std::vector results; + auto cur_amount = in_amounts.begin(); + // pre-calc reused values + bytes hashPrevouts = hash_prevouts(tx); + bytes hashSequence = hash_sequence(tx); + bytes hashOutputs = hash_outputs(tx); + // calc digest for every input according to BIP143 + // implement SIGHASH_ALL scheme + for (const auto &in : tx.vin) { + fc::sha256::encoder hasher; + hasher.write(reinterpret_cast(&tx.nVersion), sizeof(tx.nVersion)); + hasher.write(reinterpret_cast(&hashPrevouts[0]), hashPrevouts.size()); + hasher.write(reinterpret_cast(&hashSequence[0]), hashSequence.size()); + bytes data; + in.prevout.to_bytes(data); + hasher.write(reinterpret_cast(&data[0]), data.size()); + bytes serializedScript; + WriteBytesStream stream(serializedScript); + stream.writedata(redeem_script); + hasher.write(reinterpret_cast(&serializedScript[0]), serializedScript.size()); + uint64_t amount = *cur_amount++; + hasher.write(reinterpret_cast(&amount), sizeof(amount)); + hasher.write(reinterpret_cast(&in.nSequence), sizeof(in.nSequence)); + hasher.write(reinterpret_cast(&hashOutputs[0]), hashOutputs.size()); + hasher.write(reinterpret_cast(&tx.nLockTime), sizeof(tx.nLockTime)); + // add sigtype SIGHASH_ALL + uint32_t sigtype = 1; + hasher.write(reinterpret_cast(&sigtype), sizeof(sigtype)); + + fc::sha256 digest = fc::sha256::hash(hasher.result()); + //std::vector res = priv_key.sign(digest); + //bytes s_data(res.begin(), res.end()); + bytes s_data = der_sign(priv_key, digest); + s_data.push_back(1); + results.push_back(s_data); + } + return results; +} + +bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, std::vector in_amounts, const bytes &redeem_script, const std::vector> &priv_keys) { + btc_tx tx; + tx.fill_from_bytes(unsigned_tx); + bytes dummy_data; + for (auto key : priv_keys) { + if (key) { + std::vector signatures = signatures_for_raw_transaction(unsigned_tx, in_amounts, redeem_script, *key); + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + // push signatures in reverse order because script starts to check the top signature on the stack first + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); + } else { + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.push_back(dummy_data); + } + } + + for (auto &in : tx.vin) { + in.scriptWitness.push_back(redeem_script); + } + + tx.hasWitness = true; + bytes ret; + tx.to_bytes(ret); + return ret; +} + +bytes add_dummy_signatures_for_pw_transfer(const bytes &unsigned_tx, + const bytes &redeem_script, + unsigned int key_count) { + btc_tx tx; + tx.fill_from_bytes(unsigned_tx); + + bytes dummy_data; + for (auto &in : tx.vin) { + for (unsigned i = 0; i < key_count; i++) + in.scriptWitness.push_back(dummy_data); + in.scriptWitness.push_back(redeem_script); + } + + tx.hasWitness = true; + bytes ret; + tx.to_bytes(ret); + return ret; +} + +bytes partially_sign_pw_transfer_transaction(const bytes &partially_signed_tx, + std::vector in_amounts, + const fc::ecc::private_key &priv_key, + unsigned int key_idx) { + btc_tx tx; + tx.fill_from_bytes(partially_signed_tx); + FC_ASSERT(tx.vin.size() > 0); + bytes redeem_script = tx.vin[0].scriptWitness.back(); + std::vector signatures = signatures_for_raw_transaction(partially_signed_tx, in_amounts, redeem_script, priv_key); + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + // push signatures in reverse order because script starts to check the top signature on the stack first + unsigned witness_idx = tx.vin[0].scriptWitness.size() - 2 - key_idx; + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness[witness_idx] = signatures[i]; + bytes ret; + tx.to_bytes(ret); + return ret; +} + +bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, const std::vector> &signature_set, const bytes &redeem_script) { + btc_tx tx; + tx.fill_from_bytes(unsigned_tx); + bytes dummy_data; + for (unsigned int i = 0; i < signature_set.size(); i++) { + std::vector signatures = signature_set[i]; + FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number"); + // push signatures in reverse order because script starts to check the top signature on the stack first + for (unsigned int i = 0; i < tx.vin.size(); i++) + tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]); + } + + for (auto &in : tx.vin) { + in.scriptWitness.push_back(redeem_script); + } + + tx.hasWitness = true; + bytes ret; + tx.to_bytes(ret); + return ret; +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp new file mode 100644 index 00000000..9b2dc0c1 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_utils.hpp @@ -0,0 +1,104 @@ +#pragma once +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +enum bitcoin_network { + mainnet, + testnet, + regtest +}; + +bytes generate_redeem_script(std::vector> key_data); +std::string p2wsh_address_from_redeem_script(const bytes &script, bitcoin_network network = mainnet); +bytes lock_script_for_redeem_script(const bytes &script); + +std::vector signatures_for_raw_transaction(const bytes &unsigned_tx, + std::vector in_amounts, + const bytes &redeem_script, + const fc::ecc::private_key &priv_key); + +/* + * unsigned_tx - tx, all inputs of which are spends of the PW P2SH address + * returns signed transaction + */ +bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, + std::vector in_amounts, + const bytes &redeem_script, + const std::vector> &priv_keys); + +/// +////// \brief Adds dummy signatures instead of real signatures +////// \param unsigned_tx +////// \param redeem_script +////// \param key_count +////// \return can be used as partially signed tx +bytes add_dummy_signatures_for_pw_transfer(const bytes &unsigned_tx, + const bytes &redeem_script, + unsigned int key_count); + +/// +/// \brief replaces dummy sgnatures in partially signed tx with real tx +/// \param partially_signed_tx +/// \param in_amounts +/// \param priv_key +/// \param key_idx +/// \return +/// +bytes partially_sign_pw_transfer_transaction(const bytes &partially_signed_tx, + std::vector in_amounts, + const fc::ecc::private_key &priv_key, + unsigned int key_idx); + +/// +/// \brief Creates ready to publish bitcoin transaction from unsigned tx and +/// full set of the signatures. This is alternative way to create tx +/// with partially_sign_pw_transfer_transaction +/// \param unsigned_tx +/// \param signatures +/// \param redeem_script +/// \return +/// +bytes add_signatures_to_unsigned_tx(const bytes &unsigned_tx, + const std::vector> &signatures, + const bytes &redeem_script); + +struct btc_outpoint { + fc::uint256 hash; + uint32_t n; + + void to_bytes(bytes &stream) const; + size_t fill_from_bytes(const bytes &data, size_t pos = 0); +}; + +struct btc_in { + btc_outpoint prevout; + bytes scriptSig; + uint32_t nSequence; + std::vector scriptWitness; + + void to_bytes(bytes &stream) const; + size_t fill_from_bytes(const bytes &data, size_t pos = 0); +}; + +struct btc_out { + int64_t nValue; + bytes scriptPubKey; + + void to_bytes(bytes &stream) const; + size_t fill_from_bytes(const bytes &data, size_t pos = 0); +}; + +struct btc_tx { + std::vector vin; + std::vector vout; + int32_t nVersion; + uint32_t nLockTime; + bool hasWitness; + + void to_bytes(bytes &stream) const; + size_t fill_from_bytes(const bytes &data, size_t pos = 0); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp new file mode 100644 index 00000000..6dd49252 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/defs.hpp @@ -0,0 +1,72 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +using bytes = std::vector; + +struct prev_out { + bool operator!=(const prev_out &obj) const { + if (this->hash_tx != obj.hash_tx || + this->n_vout != obj.n_vout || + this->amount != obj.amount) { + return true; + } + return false; + } + + std::string hash_tx; + uint32_t n_vout; + uint64_t amount; +}; + +struct info_for_vin { + info_for_vin() = default; + + info_for_vin(const prev_out &_out, const std::string &_address, bytes _script = bytes(), bool _resend = false); + + bool operator!=(const info_for_vin &obj) const; + + struct comparer { + bool operator()(const info_for_vin &lhs, const info_for_vin &rhs) const; + }; + + static uint64_t count_id_info_for_vin; + uint64_t id; + + fc::sha256 identifier; + + prev_out out; + std::string address; + bytes script; + + bool used = false; + bool resend = false; +}; + +struct sidechain_event_data { + fc::time_point_sec timestamp; + sidechain_type sidechain; + std::string sidechain_uid; + std::string sidechain_transaction_id; + std::string sidechain_from; + std::string sidechain_to; + std::string sidechain_currency; + fc::safe sidechain_amount; + chain::account_id_type peerplays_from; + chain::account_id_type peerplays_to; + chain::asset peerplays_asset; +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp index d32fb09d..c69efaf4 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/peerplays_sidechain_plugin.hpp @@ -1,34 +1,38 @@ #pragma once #include -#include -#include -#include +#include namespace graphene { namespace peerplays_sidechain { + using namespace chain; -namespace detail -{ - class peerplays_sidechain_plugin_impl; +namespace detail { +class peerplays_sidechain_plugin_impl; } -class peerplays_sidechain_plugin : public graphene::app::plugin -{ - public: - peerplays_sidechain_plugin(); - virtual ~peerplays_sidechain_plugin(); +class peerplays_sidechain_plugin : public graphene::app::plugin { +public: + peerplays_sidechain_plugin(); + virtual ~peerplays_sidechain_plugin(); - std::string plugin_name()const override; - virtual void plugin_set_program_options( - boost::program_options::options_description& cli, - boost::program_options::options_description& cfg) override; - virtual void plugin_initialize(const boost::program_options::variables_map& options) override; - virtual void plugin_startup() override; + std::string plugin_name() const override; + virtual void plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) override; + virtual void plugin_initialize(const boost::program_options::variables_map &options) override; + virtual void plugin_startup() override; - std::unique_ptr my; + std::unique_ptr my; + + std::set &get_sons(); + const son_id_type get_current_son_id(); + const son_object get_current_son_object(); + const son_object get_son_object(son_id_type son_id); + bool is_active_son(son_id_type son_id); + fc::ecc::private_key get_private_key(son_id_type son_id); + fc::ecc::private_key get_private_key(chain::public_key_type public_key); }; -} } //graphene::peerplays_sidechain_plugin - +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp new file mode 100644 index 00000000..5814b208 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler.hpp @@ -0,0 +1,50 @@ +#pragma once + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler { +public: + sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler(); + + sidechain_type get_sidechain(); + std::vector get_sidechain_deposit_addresses(); + std::vector get_sidechain_withdraw_addresses(); + std::string get_private_key(std::string public_key); + + void sidechain_event_data_received(const sidechain_event_data &sed); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + + virtual void recreate_primary_wallet() = 0; + virtual bool process_deposit(const son_wallet_deposit_object &swdo) = 0; + virtual bool process_withdrawal(const son_wallet_withdraw_object &swwo) = 0; + virtual std::string process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete) = 0; + virtual bool send_sidechain_transaction(const sidechain_transaction_object &sto) = 0; + +protected: + peerplays_sidechain_plugin &plugin; + graphene::chain::database &database; + sidechain_type sidechain; + + std::map private_keys; + +private: +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp new file mode 100644 index 00000000..637a5254 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_bitcoin.hpp @@ -0,0 +1,123 @@ +#pragma once + +#include + +#include +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +class btc_txout { +public: + std::string txid_; + unsigned int out_num_; + double amount_; +}; + +class bitcoin_rpc_client { +public: + bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password); + + std::string addmultisigaddress(const uint32_t nrequired, const std::vector public_keys); + std::string createpsbt(const std::vector &ins, const fc::flat_map outs); + std::string createrawtransaction(const std::vector &ins, const fc::flat_map outs); + std::string createwallet(const std::string &wallet_name); + std::string decodepsbt(std::string const &tx_psbt); + std::string decoderawtransaction(std::string const &tx_hex); + std::string encryptwallet(const std::string &passphrase); + uint64_t estimatesmartfee(); + std::string finalizepsbt(std::string const &tx_psbt); + std::string getaddressinfo(const std::string &address); + std::string getblock(const std::string &block_hash, int32_t verbosity = 2); + void importaddress(const std::string &address_or_script); + std::vector listunspent(); + std::vector listunspent_by_address_and_amount(const std::string &address, double transfer_amount); + std::string loadwallet(const std::string &filename); + bool sendrawtransaction(const std::string &tx_hex); + std::string signrawtransactionwithwallet(const std::string &tx_hash); + std::string unloadwallet(const std::string &filename); + std::string walletlock(); + std::string walletprocesspsbt(std::string const &tx_psbt); + bool walletpassphrase(const std::string &passphrase, uint32_t timeout = 60); + +private: + fc::http::reply send_post_request(std::string body, bool show_log = false); + + std::string ip; + uint32_t rpc_port; + std::string user; + std::string password; + std::string wallet; + std::string wallet_password; + + fc::http::header authorization; +}; + +// ============================================================================= + +class zmq_listener { +public: + zmq_listener(std::string _ip, uint32_t _zmq); + + fc::signal event_received; + +private: + void handle_zmq(); + std::vector receive_multipart(); + + std::string ip; + uint32_t zmq_port; + + zmq::context_t ctx; + zmq::socket_t socket; +}; + +// ============================================================================= + +class sidechain_net_handler_bitcoin : public sidechain_net_handler { +public: + sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler_bitcoin(); + + void recreate_primary_wallet(); + bool process_deposit(const son_wallet_deposit_object &swdo); + bool process_withdrawal(const son_wallet_withdraw_object &swwo); + std::string process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete); + bool send_sidechain_transaction(const sidechain_transaction_object &sto); + +private: + std::string ip; + uint32_t zmq_port; + uint32_t rpc_port; + std::string rpc_user; + std::string rpc_password; + std::string wallet; + std::string wallet_password; + + std::unique_ptr bitcoin_client; + std::unique_ptr listener; + + fc::future on_changed_objects_task; + + std::string create_transaction(const std::vector &inputs, const fc::flat_map outputs); + std::string sign_transaction(const std::string &tx, bool &complete); + bool send_transaction(const std::string &tx); + + std::string create_transaction_raw(const std::vector &inputs, const fc::flat_map outputs); + std::string create_transaction_psbt(const std::vector &inputs, const fc::flat_map outputs); + std::string create_transaction_standalone(const std::vector &inputs, const fc::flat_map outputs); + + std::string sign_transaction_raw(const std::string &tx, bool &complete); + std::string sign_transaction_psbt(const std::string &tx, bool &complete); + std::string sign_transaction_standalone(const std::string &tx, bool &complete); + + void handle_event(const std::string &event_data); + std::vector extract_info_from_block(const std::string &_block); + void on_changed_objects(const vector &ids, const flat_set &accounts); + void on_changed_objects_cb(const vector &ids, const flat_set &accounts); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp new file mode 100644 index 00000000..157dc421 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_handler_peerplays.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_handler_peerplays : public sidechain_net_handler { +public: + sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options); + virtual ~sidechain_net_handler_peerplays(); + + void recreate_primary_wallet(); + bool process_deposit(const son_wallet_deposit_object &swdo); + bool process_withdrawal(const son_wallet_withdraw_object &swwo); + std::string process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete); + bool send_sidechain_transaction(const sidechain_transaction_object &sto); + +private: + void on_applied_block(const signed_block &b); +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp new file mode 100644 index 00000000..29d9c7f1 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_net_manager.hpp @@ -0,0 +1,31 @@ +#pragma once + +#include +#include +#include + +#include + +#include + +namespace graphene { namespace peerplays_sidechain { + +class sidechain_net_manager { +public: + sidechain_net_manager(peerplays_sidechain_plugin &_plugin); + virtual ~sidechain_net_manager(); + + bool create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options); + void recreate_primary_wallet(); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + +private: + peerplays_sidechain_plugin &plugin; + graphene::chain::database &database; + std::vector> net_handlers; +}; + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index 36d0b713..deda02b3 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -1,74 +1,658 @@ #include +#include +#include +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace bpo = boost::program_options; + namespace graphene { namespace peerplays_sidechain { -namespace detail -{ +namespace detail { +class peerplays_sidechain_plugin_impl { +public: + peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin &_plugin); + virtual ~peerplays_sidechain_plugin_impl(); -class peerplays_sidechain_plugin_impl -{ - public: - peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin& _plugin) - : _self( _plugin ) - { } - virtual ~peerplays_sidechain_plugin_impl(); + void plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg); + void plugin_initialize(const boost::program_options::variables_map &opt); + void plugin_startup(); - peerplays_sidechain_plugin& _self; + std::set &get_sons(); + const son_id_type get_current_son_id(); + const son_object get_current_son_object(); + const son_object get_son_object(son_id_type son_id); + bool is_active_son(son_id_type son_id); + fc::ecc::private_key get_private_key(son_id_type son_id); + fc::ecc::private_key get_private_key(chain::public_key_type public_key); - uint32_t parameter; - uint32_t optional_parameter; + void schedule_heartbeat_loop(); + void heartbeat_loop(); + void schedule_son_processing(); + void son_processing(); + void approve_proposals(); + void create_son_down_proposals(); + void create_son_deregister_proposals(); + void recreate_primary_wallet(); + void process_deposits(); + void process_withdrawals(); + void process_sidechain_transactions(); + void send_sidechain_transactions(); + +private: + peerplays_sidechain_plugin &plugin; + + boost::program_options::variables_map options; + + bool config_ready_son; + bool config_ready_bitcoin; + bool config_ready_peerplays; + + son_id_type current_son_id; + + std::unique_ptr net_manager; + std::set sons; + std::map private_keys; + fc::future _heartbeat_task; + fc::future _son_processing_task; + + bool first_block_skipped; + void on_applied_block(const signed_block &b); }; -peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() -{ - return; +peerplays_sidechain_plugin_impl::peerplays_sidechain_plugin_impl(peerplays_sidechain_plugin &_plugin) : + plugin(_plugin), + config_ready_son(false), + config_ready_bitcoin(false), + config_ready_peerplays(false), + current_son_id(son_id_type(std::numeric_limits().max())), + net_manager(nullptr), + first_block_skipped(false) { } -} // end namespace detail +peerplays_sidechain_plugin_impl::~peerplays_sidechain_plugin_impl() { + try { + if (_heartbeat_task.valid()) + _heartbeat_task.cancel_and_wait(__FUNCTION__); + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } + + try { + if (_son_processing_task.valid()) + _son_processing_task.cancel_and_wait(__FUNCTION__); + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } +} + +void peerplays_sidechain_plugin_impl::plugin_set_program_options( + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) { + auto default_priv_key = fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))); + string son_id_example = fc::json::to_string(chain::son_id_type(5)); + string son_id_example2 = fc::json::to_string(chain::son_id_type(6)); + + cli.add_options()("son-id", bpo::value>(), ("ID of SON controlled by this node (e.g. " + son_id_example + ", quotes are required)").c_str()); + cli.add_options()("son-ids", bpo::value(), ("IDs of multiple SONs controlled by this node (e.g. [" + son_id_example + ", " + son_id_example2 + "], quotes are required)").c_str()); + cli.add_options()("peerplays-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair(chain::public_key_type(default_priv_key.get_public_key()), graphene::utilities::key_to_wif(default_priv_key))), + "Tuple of [PublicKey, WIF private key] (may specify multiple times)"); + cli.add_options()("bitcoin-node-ip", bpo::value()->default_value("127.0.0.1"), "IP address of Bitcoin node"); + cli.add_options()("bitcoin-node-zmq-port", bpo::value()->default_value(11111), "ZMQ port of Bitcoin node"); + cli.add_options()("bitcoin-node-rpc-port", bpo::value()->default_value(8332), "RPC port of Bitcoin node"); + cli.add_options()("bitcoin-node-rpc-user", bpo::value()->default_value("1"), "Bitcoin RPC user"); + cli.add_options()("bitcoin-node-rpc-password", bpo::value()->default_value("1"), "Bitcoin RPC password"); + cli.add_options()("bitcoin-wallet", bpo::value(), "Bitcoin wallet"); + cli.add_options()("bitcoin-wallet-password", bpo::value(), "Bitcoin wallet password"); + cli.add_options()("bitcoin-private-key", bpo::value>()->composing()->multitoken()->DEFAULT_VALUE_VECTOR(std::make_pair("02d0f137e717fb3aab7aff99904001d49a0a636c5e1342f8927a4ba2eaee8e9772", "cVN31uC9sTEr392DLVUEjrtMgLA8Yb3fpYmTRj7bomTm6nn2ANPr")), + "Tuple of [Bitcoin public key, Bitcoin private key] (may specify multiple times)"); + cfg.add(cli); +} + +void peerplays_sidechain_plugin_impl::plugin_initialize(const boost::program_options::variables_map &opt) { + options = opt; + config_ready_son = (options.count("son-id") || options.count("son-ids")) && options.count("peerplays-private-key"); + if (config_ready_son) { + LOAD_VALUE_SET(options, "son-id", sons, chain::son_id_type) + if (options.count("son-ids")) + boost::insert(sons, fc::json::from_string(options.at("son-ids").as()).as>(5)); + config_ready_son = config_ready_son && !sons.empty(); + +#ifndef SUPPORT_MULTIPLE_SONS + FC_ASSERT(sons.size() == 1, "Multiple SONs not supported"); +#endif + + if (options.count("peerplays-private-key")) { + const std::vector key_id_to_wif_pair_strings = options["peerplays-private-key"].as>(); + for (const std::string &key_id_to_wif_pair_string : key_id_to_wif_pair_strings) { + auto key_id_to_wif_pair = graphene::app::dejsonify>(key_id_to_wif_pair_string, 5); + ilog("Public Key: ${public}", ("public", key_id_to_wif_pair.first)); + fc::optional private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second); + if (!private_key) { + // the key isn't in WIF format; see if they are still passing the old native private key format. This is + // just here to ease the transition, can be removed soon + try { + private_key = fc::variant(key_id_to_wif_pair.second, 2).as(1); + } catch (const fc::exception &) { + FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second)); + } + } + private_keys[key_id_to_wif_pair.first] = *private_key; + } + config_ready_son = config_ready_son && !private_keys.empty(); + } + } + if (!config_ready_son) { + wlog("Haven't set up SON parameters"); + throw; + } + + config_ready_bitcoin = options.count("bitcoin-node-ip") && + options.count("bitcoin-node-zmq-port") && options.count("bitcoin-node-rpc-port") && + options.count("bitcoin-node-rpc-user") && options.count("bitcoin-node-rpc-password") && + /*options.count( "bitcoin-wallet" ) && options.count( "bitcoin-wallet-password" ) &&*/ + options.count("bitcoin-private-key"); + if (!config_ready_bitcoin) { + wlog("Haven't set up Bitcoin sidechain parameters"); + } + + //config_ready_ethereum = options.count( "ethereum-node-ip" ) && + // options.count( "ethereum-address" ) && options.count( "ethereum-public-key" ) && options.count( "ethereum-private-key" ); + //if (!config_ready_ethereum) { + // wlog("Haven't set up Ethereum sidechain parameters"); + //} + + config_ready_peerplays = true; + if (!config_ready_peerplays) { + wlog("Haven't set up Peerplays sidechain parameters"); + } + + if (!(config_ready_bitcoin /*&& config_ready_ethereum*/ && config_ready_peerplays)) { + wlog("Haven't set up any sidechain parameters"); + throw; + } +} + +void peerplays_sidechain_plugin_impl::plugin_startup() { + + if (config_ready_son) { + ilog("Starting ${n} SON instances", ("n", sons.size())); + + schedule_heartbeat_loop(); + } else { + elog("No sons configured! Please add SON IDs and private keys to configuration."); + } + + net_manager = std::unique_ptr(new sidechain_net_manager(plugin)); + + if (config_ready_bitcoin) { + net_manager->create_handler(sidechain_type::bitcoin, options); + ilog("Bitcoin sidechain handler running"); + } + + //if (config_ready_ethereum) { + // net_manager->create_handler(sidechain_type::ethereum, options); + // ilog("Ethereum sidechain handler running"); + //} + + if (config_ready_peerplays) { + net_manager->create_handler(sidechain_type::peerplays, options); + ilog("Peerplays sidechain handler running"); + } + + plugin.database().applied_block.connect([&](const signed_block &b) { + on_applied_block(b); + }); +} + +std::set &peerplays_sidechain_plugin_impl::get_sons() { + return sons; +} + +const son_id_type peerplays_sidechain_plugin_impl::get_current_son_id() { + return current_son_id; +} + +const son_object peerplays_sidechain_plugin_impl::get_current_son_object() { + return get_son_object(current_son_id); +} + +const son_object peerplays_sidechain_plugin_impl::get_son_object(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return {}; + return *son_obj; +} + +bool peerplays_sidechain_plugin_impl::is_active_son(son_id_type son_id) { + const auto &idx = plugin.database().get_index_type().indices().get(); + auto son_obj = idx.find(son_id); + if (son_obj == idx.end()) + return false; + + const chain::global_property_object &gpo = plugin.database().get_global_properties(); + vector active_son_ids; + active_son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(active_son_ids, active_son_ids.end()), + [](const son_info &swi) { + return swi.son_id; + }); + + auto it = std::find(active_son_ids.begin(), active_son_ids.end(), son_id); + + return (it != active_son_ids.end()); +} + +fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(son_id_type son_id) { + return get_private_key(get_son_object(son_id).signing_key); +} + +fc::ecc::private_key peerplays_sidechain_plugin_impl::get_private_key(chain::public_key_type public_key) { + auto private_key_itr = private_keys.find(public_key); + if (private_key_itr != private_keys.end()) { + return private_key_itr->second; + } + return {}; +} + +void peerplays_sidechain_plugin_impl::schedule_heartbeat_loop() { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_heartbeat = plugin.database().get_global_properties().parameters.son_heartbeat_frequency(); + + fc::time_point next_wakeup(now + fc::seconds(time_to_next_heartbeat)); + + _heartbeat_task = fc::schedule([this] { + heartbeat_loop(); + }, + next_wakeup, "SON Heartbeat Production"); +} + +void peerplays_sidechain_plugin_impl::heartbeat_loop() { + schedule_heartbeat_loop(); + chain::database &d = plugin.database(); + + for (son_id_type son_id : sons) { + if (is_active_son(son_id) || get_son_object(son_id).status == chain::son_status::in_maintenance) { + + ilog("Sending heartbeat for SON ${son}", ("son", son_id)); + chain::son_heartbeat_operation op; + op.owner_account = get_son_object(son_id).son_account; + op.son_id = son_id; + op.ts = fc::time_point::now() + fc::seconds(0); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(son_id), op); + fc::future fut = fc::async([&]() { + try { + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending heartbeat failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } +} + +void peerplays_sidechain_plugin_impl::schedule_son_processing() { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_son_processing = 500000; + + fc::time_point next_wakeup(now + fc::microseconds(time_to_next_son_processing)); + + _son_processing_task = fc::schedule([this] { + son_processing(); + }, + next_wakeup, "SON Processing"); +} + +void peerplays_sidechain_plugin_impl::son_processing() { + if (plugin.database().get_global_properties().active_sons.size() <= 0) { + return; + } + + fc::time_point now_fine = fc::time_point::now(); + fc::time_point_sec now = now_fine + fc::microseconds(500000); + if (plugin.database().get_slot_time(1) < now) { + return; // Not synced + } + + chain::son_id_type scheduled_son_id = plugin.database().get_scheduled_son(1); + ilog("Scheduled SON: ${scheduled_son_id} Now: ${now} ", + ("scheduled_son_id", scheduled_son_id)("now", now)); + + for (son_id_type son_id : plugin.get_sons()) { + + if (plugin.is_active_son(son_id)) { + + current_son_id = son_id; + + // Tasks that are executed by all active SONs, no matter if scheduled + // E.g. sending approvals and signing (only signing that can be done in parallel) + approve_proposals(); + + // Tasks that are executed by scheduled and active SON + if (current_son_id == scheduled_son_id) { + + create_son_down_proposals(); + + create_son_deregister_proposals(); + + recreate_primary_wallet(); + + process_deposits(); + + process_withdrawals(); + + process_sidechain_transactions(); + + send_sidechain_transactions(); + } + } else { + // Tasks that are executed by previously active SONs + // E.g. sending approvals and signing that SON was required to do while it was active + //approve_leftover_proposals(); ??? + //process_leftover_sidechain_transactions(); ??? + } + } +} + +void peerplays_sidechain_plugin_impl::approve_proposals() { + + auto approve_proposal = [&](const chain::son_id_type &son_id, const chain::proposal_id_type &proposal_id) { + ilog("Sending approval for ${p} from ${s}", ("p", proposal_id)("s", son_id)); + chain::proposal_update_operation puo; + puo.fee_paying_account = get_son_object(son_id).son_account; + puo.proposal = proposal_id; + puo.active_approvals_to_add = {get_son_object(son_id).son_account}; + chain::signed_transaction trx = plugin.database().create_signed_transaction(plugin.get_private_key(son_id), puo); + fc::future fut = fc::async([&]() { + try { + plugin.database().push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending approval failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + }; + + const auto &idx = plugin.database().get_index_type().indices().get(); + vector proposals; + for (const auto &proposal : idx) { + proposals.push_back(proposal.id); + } + + for (const auto proposal_id : proposals) { + + const object *obj = plugin.database().find_object(proposal_id); + const chain::proposal_object *proposal_ptr = dynamic_cast(obj); + if (proposal_ptr == nullptr) { + continue; + } + const proposal_object proposal = *proposal_ptr; + + if (proposal.available_active_approvals.find(get_current_son_object().son_account) != proposal.available_active_approvals.end()) { + continue; + } + + if (proposal.proposed_transaction.operations.size() == 1) { + int32_t op_idx_0 = proposal.proposed_transaction.operations[0].which(); + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + + if (op_idx_0 == chain::operation::tag::value) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + } + + if (proposal.proposed_transaction.operations.size() == 2) { + int32_t op_idx_0 = proposal.proposed_transaction.operations[0].which(); + int32_t op_idx_1 = proposal.proposed_transaction.operations[1].which(); + + if ((op_idx_0 == chain::operation::tag::value) && + (op_idx_1 == chain::operation::tag::value)) { + approve_proposal(get_current_son_id(), proposal.id); + continue; + } + } + + ilog("=================================================="); + ilog("Proposal not approved ${proposal}", ("proposal", proposal)); + ilog("=================================================="); + } +} + +void peerplays_sidechain_plugin_impl::create_son_down_proposals() { + auto create_son_down_proposal = [&](chain::son_id_type son_id, fc::time_point_sec last_active_ts) { + chain::database &d = plugin.database(); + const chain::global_property_object &gpo = d.get_global_properties(); + + chain::son_report_down_operation son_down_op; + son_down_op.payer = d.get_global_properties().parameters.son_account(); + son_down_op.son_id = son_id; + son_down_op.down_ts = last_active_ts; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(op_wrapper(son_down_op)); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(d.head_block_time().sec_since_epoch() + lifetime); + return proposal_op; + }; + + chain::database &d = plugin.database(); + const chain::global_property_object &gpo = d.get_global_properties(); + const chain::dynamic_global_property_object &dgpo = d.get_dynamic_global_properties(); + const auto &idx = d.get_index_type().indices().get(); + std::set sons_being_reported_down = d.get_sons_being_reported_down(); + chain::son_id_type my_son_id = get_current_son_id(); + for (auto son_inf : gpo.active_sons) { + if (my_son_id == son_inf.son_id || (sons_being_reported_down.find(son_inf.son_id) != sons_being_reported_down.end())) { + continue; + } + auto son_obj = idx.find(son_inf.son_id); + auto stats = son_obj->statistics(d); + fc::time_point_sec last_maintenance_time = dgpo.next_maintenance_time - gpo.parameters.maintenance_interval; + fc::time_point_sec last_active_ts = ((stats.last_active_timestamp > last_maintenance_time) ? stats.last_active_timestamp : last_maintenance_time); + int64_t down_threshold = gpo.parameters.son_down_time(); + if (((son_obj->status == chain::son_status::active) || (son_obj->status == chain::son_status::request_maintenance)) && + ((fc::time_point::now() - last_active_ts) > fc::seconds(down_threshold))) { + ilog("Sending son down proposal for ${t} from ${s}", ("t", std::string(object_id_type(son_obj->id)))("s", std::string(object_id_type(my_son_id)))); + chain::proposal_create_operation op = create_son_down_proposal(son_inf.son_id, last_active_ts); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), op); + fc::future fut = fc::async([&]() { + try { + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending son down proposal failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } +} + +void peerplays_sidechain_plugin_impl::create_son_deregister_proposals() { + chain::database &d = plugin.database(); + std::set sons_to_be_dereg = d.get_sons_to_be_deregistered(); + chain::son_id_type my_son_id = get_current_son_id(); + + if (sons_to_be_dereg.size() > 0) { + // We shouldn't raise proposals for the SONs for which a de-reg + // proposal is already raised. + std::set sons_being_dereg = d.get_sons_being_deregistered(); + for (auto &son : sons_to_be_dereg) { + // New SON to be deregistered + if (sons_being_dereg.find(son) == sons_being_dereg.end() && my_son_id != son) { + // Creating the de-reg proposal + auto op = d.create_son_deregister_proposal(son, get_son_object(my_son_id).son_account); + if (op.valid()) { + // Signing and pushing into the txs to be included in the block + ilog("Sending son deregister proposal for ${p} from ${s}", ("p", son)("s", my_son_id)); + chain::signed_transaction trx = d.create_signed_transaction(plugin.get_private_key(get_son_object(my_son_id).signing_key), *op); + fc::future fut = fc::async([&]() { + try { + d.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending son deregister proposal failed with exception ${e}", ("e", e.what())); + return false; + } + }); + fut.wait(fc::seconds(10)); + } + } + } + } +} + +void peerplays_sidechain_plugin_impl::recreate_primary_wallet() { + net_manager->recreate_primary_wallet(); +} + +void peerplays_sidechain_plugin_impl::process_deposits() { + net_manager->process_deposits(); +} + +void peerplays_sidechain_plugin_impl::process_withdrawals() { + net_manager->process_withdrawals(); +} + +void peerplays_sidechain_plugin_impl::process_sidechain_transactions() { + net_manager->process_sidechain_transactions(); +} + +void peerplays_sidechain_plugin_impl::send_sidechain_transactions() { + net_manager->send_sidechain_transactions(); +} + +void peerplays_sidechain_plugin_impl::on_applied_block(const signed_block &b) { + if (first_block_skipped) { + schedule_son_processing(); + } else { + first_block_skipped = true; + } +} + +} // namespace detail peerplays_sidechain_plugin::peerplays_sidechain_plugin() : - my( new detail::peerplays_sidechain_plugin_impl(*this) ) -{ + my(new detail::peerplays_sidechain_plugin_impl(*this)) { } -peerplays_sidechain_plugin::~peerplays_sidechain_plugin() -{ +peerplays_sidechain_plugin::~peerplays_sidechain_plugin() { return; } -std::string peerplays_sidechain_plugin::plugin_name()const -{ +std::string peerplays_sidechain_plugin::plugin_name() const { return "peerplays_sidechain"; } void peerplays_sidechain_plugin::plugin_set_program_options( - boost::program_options::options_description& cli, - boost::program_options::options_description& cfg - ) -{ - cli.add_options() - ("parameter", boost::program_options::value(), "Parameter") - ("optional-parameter", boost::program_options::value(), "Optional parameter") - ; - cfg.add(cli); + boost::program_options::options_description &cli, + boost::program_options::options_description &cfg) { + my->plugin_set_program_options(cli, cfg); } -void peerplays_sidechain_plugin::plugin_initialize(const boost::program_options::variables_map& options) -{ - ilog("peerplays sidechain plugin: plugin_initialize()"); - - if (options.count("parameter")) { - my->parameter = options["optional-parameter"].as(); - } - if (options.count("optional-parameter")) { - my->optional_parameter = options["optional-parameter"].as(); - } +void peerplays_sidechain_plugin::plugin_initialize(const boost::program_options::variables_map &options) { + ilog("peerplays sidechain plugin: plugin_initialize() begin"); + my->plugin_initialize(options); + ilog("peerplays sidechain plugin: plugin_initialize() end"); } -void peerplays_sidechain_plugin::plugin_startup() -{ - ilog("peerplays sidechain plugin: plugin_startup()"); +void peerplays_sidechain_plugin::plugin_startup() { + ilog("peerplays sidechain plugin: plugin_startup() begin"); + my->plugin_startup(); + ilog("peerplays sidechain plugin: plugin_startup() end"); } -} } +std::set &peerplays_sidechain_plugin::get_sons() { + return my->get_sons(); +} + +const son_id_type peerplays_sidechain_plugin::get_current_son_id() { + return my->get_current_son_id(); +} + +const son_object peerplays_sidechain_plugin::get_current_son_object() { + return my->get_current_son_object(); +} + +const son_object peerplays_sidechain_plugin::get_son_object(son_id_type son_id) { + return my->get_son_object(son_id); +} + +bool peerplays_sidechain_plugin::is_active_son(son_id_type son_id) { + return my->is_active_son(son_id); +} + +fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(son_id_type son_id) { + return my->get_private_key(son_id); +} + +fc::ecc::private_key peerplays_sidechain_plugin::get_private_key(chain::public_key_type public_key) { + return my->get_private_key(public_key); +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp new file mode 100644 index 00000000..c6eccd12 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler.cpp @@ -0,0 +1,327 @@ +#include + +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler::sidechain_net_handler(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + plugin(_plugin), + database(_plugin.database()) { +} + +sidechain_net_handler::~sidechain_net_handler() { +} + +sidechain_type sidechain_net_handler::get_sidechain() { + return sidechain; +} + +std::vector sidechain_net_handler::get_sidechain_deposit_addresses() { + std::vector result; + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&result](const sidechain_address_object &sao) { + result.push_back(sao.deposit_address); + }); + return result; +} + +std::vector sidechain_net_handler::get_sidechain_withdraw_addresses() { + std::vector result; + + const auto &sidechain_addresses_idx = database.get_index_type(); + const auto &sidechain_addresses_by_sidechain_idx = sidechain_addresses_idx.indices().get(); + const auto &sidechain_addresses_by_sidechain_range = sidechain_addresses_by_sidechain_idx.equal_range(sidechain); + std::for_each(sidechain_addresses_by_sidechain_range.first, sidechain_addresses_by_sidechain_range.second, + [&result](const sidechain_address_object &sao) { + result.push_back(sao.withdraw_address); + }); + return result; +} + +std::string sidechain_net_handler::get_private_key(std::string public_key) { + auto private_key_itr = private_keys.find(public_key); + if (private_key_itr != private_keys.end()) { + return private_key_itr->second; + } + return std::string(); +} + +void sidechain_net_handler::sidechain_event_data_received(const sidechain_event_data &sed) { + ilog("sidechain_event_data:"); + ilog(" timestamp: ${timestamp}", ("timestamp", sed.timestamp)); + ilog(" sidechain: ${sidechain}", ("sidechain", sed.sidechain)); + ilog(" sidechain_uid: ${uid}", ("uid", sed.sidechain_uid)); + ilog(" sidechain_transaction_id: ${transaction_id}", ("transaction_id", sed.sidechain_transaction_id)); + ilog(" sidechain_from: ${from}", ("from", sed.sidechain_from)); + ilog(" sidechain_to: ${to}", ("to", sed.sidechain_to)); + ilog(" sidechain_currency: ${currency}", ("currency", sed.sidechain_currency)); + ilog(" sidechain_amount: ${amount}", ("amount", sed.sidechain_amount)); + ilog(" peerplays_from: ${peerplays_from}", ("peerplays_from", sed.peerplays_from)); + ilog(" peerplays_to: ${peerplays_to}", ("peerplays_to", sed.peerplays_to)); + ilog(" peerplays_asset: ${peerplays_asset}", ("peerplays_asset", sed.peerplays_asset)); + + const chain::global_property_object &gpo = database.get_global_properties(); + + // Deposit request + if ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare("1.3.0") != 0)) { + son_wallet_deposit_create_operation op; + op.payer = gpo.parameters.son_account(); + //op.son_id = ; // to be filled for each son + op.timestamp = sed.timestamp; + op.sidechain = sed.sidechain; + op.sidechain_uid = sed.sidechain_uid; + op.sidechain_transaction_id = sed.sidechain_transaction_id; + op.sidechain_from = sed.sidechain_from; + op.sidechain_to = sed.sidechain_to; + op.sidechain_currency = sed.sidechain_currency; + op.sidechain_amount = sed.sidechain_amount; + op.peerplays_from = sed.peerplays_from; + op.peerplays_to = sed.peerplays_to; + op.peerplays_asset = sed.peerplays_asset; + + for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_active_son(son_id)) { + + op.son_id = son_id; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_son_object(son_id).son_account; + proposal_op.proposed_ops.emplace_back(op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(son_id), proposal_op); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for son wallet deposit create operation by ${son} failed with exception ${e}", ("son", son_id)("e", e.what())); + } + } + } + return; + } + + // Withdrawal request + if ((sed.peerplays_to == gpo.parameters.son_account()) && (sed.sidechain_currency.compare("1.3.0") == 0)) { + // BTC Payout only (for now) + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sed.peerplays_from, sidechain_type::bitcoin)); + if (addr_itr == sidechain_addresses_idx.end()) + return; + + son_wallet_withdraw_create_operation op; + op.payer = gpo.parameters.son_account(); + //op.son_id = ; // to be filled for each son + op.timestamp = sed.timestamp; + op.sidechain = sed.sidechain; + op.peerplays_uid = sed.sidechain_uid; + op.peerplays_transaction_id = sed.sidechain_transaction_id; + op.peerplays_from = sed.peerplays_from; + op.peerplays_asset = sed.peerplays_asset; + op.withdraw_sidechain = sidechain_type::bitcoin; // BTC payout only (for now) + op.withdraw_address = addr_itr->withdraw_address; // BTC payout only (for now) + op.withdraw_currency = "BTC"; // BTC payout only (for now) + op.withdraw_amount = sed.peerplays_asset.amount * 1000; // BTC payout only (for now) + + for (son_id_type son_id : plugin.get_sons()) { + if (plugin.is_active_son(son_id)) { + + op.son_id = son_id; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_son_object(son_id).son_account; + proposal_op.proposed_ops.emplace_back(op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(son_id), proposal_op); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for son wallet withdraw create operation by ${son} failed with exception ${e}", ("son", son_id)("e", e.what())); + } + } + } + return; + } + + FC_ASSERT(false, "Invalid sidechain event"); +} + +void sidechain_net_handler::process_deposits() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_deposit_object &swdo) { + ilog("Deposit to process: ${swdo}", ("swdo", swdo)); + + bool process_deposit_result = process_deposit(swdo); + + if (!process_deposit_result) { + wlog("Deposit not processed: ${swdo}", ("swdo", swdo)); + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + son_wallet_deposit_process_operation swdp_op; + swdp_op.payer = gpo.parameters.son_account(); + swdp_op.son_wallet_deposit_id = swdo.id; + + transfer_operation t_op; + t_op.fee = asset(2000000); + t_op.from = swdo.peerplays_to; // gpo.parameters.son_account() + t_op.to = swdo.peerplays_from; + t_op.amount = swdo.peerplays_asset; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(swdp_op); + proposal_op.proposed_ops.emplace_back(t_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::process_withdrawals() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const son_wallet_withdraw_object &swwo) { + ilog("Withdraw to process: ${swwo}", ("swwo", swwo)); + + bool process_withdrawal_result = process_withdrawal(swwo); + + if (!process_withdrawal_result) { + wlog("Withdraw not processed: ${swwo}", ("swwo", swwo)); + return; + } + + const chain::global_property_object &gpo = database.get_global_properties(); + + son_wallet_withdraw_process_operation swwp_op; + swwp_op.payer = gpo.parameters.son_account(); + swwp_op.son_wallet_withdraw_id = swwo.id; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(swwp_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for withdraw sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::process_sidechain_transactions() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + ilog("Sidechain transaction to process: ${sto}", ("sto", sto)); + + bool complete = false; + std::string processed_sidechain_tx = process_sidechain_transaction(sto, complete); + + if (processed_sidechain_tx.empty()) { + wlog("Sidechain transaction not processed: ${sto}", ("sto", sto)); + return; + } + + sidechain_transaction_sign_operation sts_op; + sts_op.payer = plugin.get_current_son_object().son_account; + sts_op.sidechain_transaction_id = sto.id; + sts_op.transaction = processed_sidechain_tx; + sts_op.block = sto.block; + sts_op.complete = complete; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), sts_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for sidechain transaction sign operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::send_sidechain_transactions() { + const auto &idx = database.get_index_type().indices().get(); + const auto &idx_range = idx.equal_range(std::make_tuple(sidechain, true, false)); + + std::for_each(idx_range.first, idx_range.second, [&](const sidechain_transaction_object &sto) { + ilog("Sidechain transaction to send: ${sto}", ("sto", sto)); + + bool sent = send_sidechain_transaction(sto); + + if (!sent) { + wlog("Sidechain transaction not sent: ${sto}", ("sto", sto)); + return; + } + + sidechain_transaction_send_operation sts_op; + sts_op.payer = plugin.get_current_son_object().son_account; + sts_op.sidechain_transaction_id = sto.id; + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), sts_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for sidechain transaction send operation failed with exception ${e}", ("e", e.what())); + } + }); +} + +void sidechain_net_handler::recreate_primary_wallet() { + FC_ASSERT(false, "recreate_primary_wallet not implemented"); +} + +bool sidechain_net_handler::process_deposit(const son_wallet_deposit_object &swdo) { + FC_ASSERT(false, "process_deposit not implemented"); +} + +bool sidechain_net_handler::process_withdrawal(const son_wallet_withdraw_object &swwo) { + FC_ASSERT(false, "process_withdrawal not implemented"); +} + +std::string sidechain_net_handler::process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete) { + FC_ASSERT(false, "process_sidechain_transaction not implemented"); +} + +bool sidechain_net_handler::send_sidechain_transaction(const sidechain_transaction_object &sto) { + FC_ASSERT(false, "send_sidechain_transaction not implemented"); +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp new file mode 100644 index 00000000..a788b04f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_bitcoin.cpp @@ -0,0 +1,1416 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +// ============================================================================= + +bitcoin_rpc_client::bitcoin_rpc_client(std::string _ip, uint32_t _rpc, std::string _user, std::string _password, std::string _wallet, std::string _wallet_password) : + ip(_ip), + rpc_port(_rpc), + user(_user), + password(_password), + wallet(_wallet), + wallet_password(_wallet_password) { + authorization.key = "Authorization"; + authorization.val = "Basic " + fc::base64_encode(user + ":" + password); +} + +std::string bitcoin_rpc_client::addmultisigaddress(const uint32_t nrequired, const std::vector public_keys) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"addmultisigaddress\", " + "\"method\": \"addmultisigaddress\", \"params\": ["); + std::string params = std::to_string(nrequired) + ", ["; + std::string pubkeys = ""; + for (std::string pubkey : public_keys) { + if (!pubkeys.empty()) { + pubkeys = pubkeys + ","; + } + pubkeys = pubkeys + std::string("\"") + pubkey + std::string("\""); + } + params = params + pubkeys + std::string("]"); + body = body + params + std::string("] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createpsbt(const std::vector &ins, const fc::flat_map outs) { + std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createpsbt\", " + "\"method\": \"createpsbt\", \"params\": ["); + body += "["; + bool first = true; + for (const auto &entry : ins) { + if (!first) + body += ","; + body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; + first = false; + } + body += "],["; + first = true; + for (const auto &entry : outs) { + if (!first) + body += ","; + body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; + first = false; + } + body += std::string("]] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + return json.get("result"); + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createrawtransaction(const std::vector &ins, const fc::flat_map outs) { + std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"createrawtransaction\", " + "\"method\": \"createrawtransaction\", \"params\": ["); + body += "["; + bool first = true; + for (const auto &entry : ins) { + if (!first) + body += ","; + body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":" + std::to_string(entry.out_num_) + "}"; + first = false; + } + body += "],["; + first = true; + for (const auto &entry : outs) { + if (!first) + body += ","; + body += "{\"" + entry.first + "\":" + std::to_string(entry.second) + "}"; + first = false; + } + body += std::string("]] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + return json.get("result"); + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::createwallet(const std::string &wallet_name) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"createwallet\", \"method\": " + "\"createwallet\", \"params\": [\"" + + wallet_name + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::decodepsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decodepsbt\", \"method\": " + "\"decodepsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::decoderawtransaction(std::string const &tx_hex) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"decoderawtransaction\", \"method\": " + "\"decoderawtransaction\", \"params\": [\"" + + tx_hex + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::encryptwallet(const std::string &passphrase) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"encryptwallet\", \"method\": " + "\"encryptwallet\", \"params\": [\"" + + passphrase + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +uint64_t bitcoin_rpc_client::estimatesmartfee() { + static const auto confirmation_target_blocks = 6; + + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimatesmartfee\", " + "\"method\": \"estimatesmartfee\", \"params\": [") + + std::to_string(confirmation_target_blocks) + std::string("] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return 0; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.find("result") != json.not_found()) { + auto json_result = json.get_child("result"); + if (json_result.find("feerate") != json_result.not_found()) { + auto feerate_str = json_result.get("feerate"); + feerate_str.erase(std::remove(feerate_str.begin(), feerate_str.end(), '.'), feerate_str.end()); + return std::stoll(feerate_str); + } + + if (json_result.find("errors") != json_result.not_found()) { + wlog("Bitcoin RPC call ${function} with body ${body} executed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + } + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return 20000; +} + +std::string bitcoin_rpc_client::finalizepsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"finalizepsbt\", \"method\": " + "\"finalizepsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getaddressinfo(const std::string &address) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getaddressinfo\", \"method\": " + "\"getaddressinfo\", \"params\": [\"" + + address + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::getblock(const std::string &block_hash, int32_t verbosity) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"getblock\", \"method\": " + "\"getblock\", \"params\": [\"" + + block_hash + "\", " + std::to_string(verbosity) + "] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +void bitcoin_rpc_client::importaddress(const std::string &address_or_script) { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"importaddress\", " + "\"method\": \"importaddress\", \"params\": [") + + std::string("\"") + address_or_script + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return; + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } +} + +std::vector bitcoin_rpc_client::listunspent() { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " + "\"listunspent\", \"params\": [] }"); + + const auto reply = send_post_request(body); + + std::vector result; + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return result; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + txo.amount_ = entry.second.get_child("amount").get_value(); + result.push_back(txo); + } + } + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return result; +} + +std::vector bitcoin_rpc_client::listunspent_by_address_and_amount(const std::string &address, double minimum_amount) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": " + "\"listunspent\", \"params\": ["); + body += std::string("1,999999,[\""); + body += address; + body += std::string("\"],true,{\"minimumAmount\":"); + body += std::to_string(minimum_amount); + body += std::string("}] }"); + + const auto reply = send_post_request(body); + + std::vector result; + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return result; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + if (json.count("result")) { + for (auto &entry : json.get_child("result")) { + btc_txout txo; + txo.txid_ = entry.second.get_child("txid").get_value(); + txo.out_num_ = entry.second.get_child("vout").get_value(); + txo.amount_ = entry.second.get_child("amount").get_value(); + result.push_back(txo); + } + } + } else if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return result; +} + +std::string bitcoin_rpc_client::loadwallet(const std::string &filename) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"loadwallet\", \"method\": " + "\"loadwallet\", \"params\": [\"" + + filename + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +bool bitcoin_rpc_client::sendrawtransaction(const std::string &tx_hex) { + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"sendrawtransaction\", " + "\"method\": \"sendrawtransaction\", \"params\": [") + + std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return false; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return true; + } else if (json.count("error") && !json.get_child("error").empty()) { + const auto error_code = json.get_child("error").get_child("code").get_value(); + if (error_code == -27) // transaction already in block chain + return true; + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return false; +} + +std::string bitcoin_rpc_client::signrawtransactionwithwallet(const std::string &tx_hash) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"signrawtransactionwithwallet\", " + "\"method\": \"signrawtransactionwithwallet\", \"params\": ["); + std::string params = "\"" + tx_hash + "\""; + body = body + params + std::string("]}"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::unloadwallet(const std::string &filename) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"unloadwallet\", \"method\": " + "\"unloadwallet\", \"params\": [\"" + + filename + "\"] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::walletlock() { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletlock\", \"method\": " + "\"walletlock\", \"params\": [] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + std::stringstream ss; + boost::property_tree::json_parser::write_json(ss, json.get_child("result")); + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +std::string bitcoin_rpc_client::walletprocesspsbt(std::string const &tx_psbt) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletprocesspsbt\", \"method\": " + "\"walletprocesspsbt\", \"params\": [\"" + + tx_psbt + "\"] }"); + + const auto reply = send_post_request(body, true); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return ""; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return ss.str(); + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return ""; +} + +bool bitcoin_rpc_client::walletpassphrase(const std::string &passphrase, uint32_t timeout) { + std::string body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"walletpassphrase\", \"method\": " + "\"walletpassphrase\", \"params\": [\"" + + passphrase + "\", " + std::to_string(timeout) + "] }"); + + const auto reply = send_post_request(body); + + if (reply.body.empty()) { + wlog("Bitcoin RPC call ${function} failed", ("function", __FUNCTION__)); + return false; + } + + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + if (reply.status == 200) { + return true; + } + + if (json.count("error") && !json.get_child("error").empty()) { + wlog("Bitcoin RPC call ${function} with body ${body} failed with reply '${msg}'", ("function", __FUNCTION__)("body", body)("msg", ss.str())); + } + return false; +} + +fc::http::reply bitcoin_rpc_client::send_post_request(std::string body, bool show_log) { + fc::http::connection conn; + conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); + + std::string url = "http://" + ip + ":" + std::to_string(rpc_port); + + if (wallet.length() > 0) { + url = url + "/wallet/" + wallet; + } + + fc::http::reply reply = conn.request("POST", url, body, fc::http::headers{authorization}); + + if (show_log) { + ilog("### Request URL: ${url}", ("url", url)); + ilog("### Request: ${body}", ("body", body)); + std::stringstream ss(std::string(reply.body.begin(), reply.body.end())); + ilog("### Response: ${ss}", ("ss", ss.str())); + } + + return reply; +} + +// ============================================================================= + +zmq_listener::zmq_listener(std::string _ip, uint32_t _zmq) : + ip(_ip), + zmq_port(_zmq), + ctx(1), + socket(ctx, ZMQ_SUB) { + std::thread(&zmq_listener::handle_zmq, this).detach(); +} + +std::vector zmq_listener::receive_multipart() { + std::vector msgs; + + int32_t more; + size_t more_size = sizeof(more); + while (true) { + zmq::message_t msg; + socket.recv(&msg, 0); + socket.getsockopt(ZMQ_RCVMORE, &more, &more_size); + + if (!more) + break; + msgs.push_back(std::move(msg)); + } + + return msgs; +} + +void zmq_listener::handle_zmq() { + int linger = 0; + socket.setsockopt(ZMQ_SUBSCRIBE, "hashblock", 9); + socket.setsockopt(ZMQ_LINGER, &linger, sizeof(linger)); + //socket.setsockopt( ZMQ_SUBSCRIBE, "hashtx", 6 ); + //socket.setsockopt( ZMQ_SUBSCRIBE, "rawblock", 8 ); + //socket.setsockopt( ZMQ_SUBSCRIBE, "rawtx", 5 ); + socket.connect("tcp://" + ip + ":" + std::to_string(zmq_port)); + + while (true) { + try { + auto msg = receive_multipart(); + const auto header = std::string(static_cast(msg[0].data()), msg[0].size()); + const auto block_hash = boost::algorithm::hex(std::string(static_cast(msg[1].data()), msg[1].size())); + event_received(block_hash); + } catch (zmq::error_t &e) { + } + } +} + +// ============================================================================= + +sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain_net_handler(_plugin, options) { + sidechain = sidechain_type::bitcoin; + + ip = options.at("bitcoin-node-ip").as(); + zmq_port = options.at("bitcoin-node-zmq-port").as(); + rpc_port = options.at("bitcoin-node-rpc-port").as(); + rpc_user = options.at("bitcoin-node-rpc-user").as(); + rpc_password = options.at("bitcoin-node-rpc-password").as(); + wallet = ""; + if (options.count("bitcoin-wallet")) { + wallet = options.at("bitcoin-wallet").as(); + } + wallet_password = ""; + if (options.count("bitcoin-wallet-password")) { + wallet_password = options.at("bitcoin-wallet-password").as(); + } + + if (options.count("bitcoin-private-key")) { + const std::vector pub_priv_keys = options["bitcoin-private-key"].as>(); + for (const std::string &itr_key_pair : pub_priv_keys) { + auto key_pair = graphene::app::dejsonify>(itr_key_pair, 5); + ilog("Bitcoin Public Key: ${public}", ("public", key_pair.first)); + if (!key_pair.first.length() || !key_pair.second.length()) { + FC_THROW("Invalid public private key pair."); + } + private_keys[key_pair.first] = key_pair.second; + } + } + + fc::http::connection conn; + try { + conn.connect_to(fc::ip::endpoint(fc::ip::address(ip), rpc_port)); + } catch (fc::exception e) { + elog("No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", ip)("port", rpc_port)); + FC_ASSERT(false); + } + + bitcoin_client = std::unique_ptr(new bitcoin_rpc_client(ip, rpc_port, rpc_user, rpc_password, wallet, wallet_password)); + if (!wallet.empty()) { + bitcoin_client->loadwallet(wallet); + } + + listener = std::unique_ptr(new zmq_listener(ip, zmq_port)); + listener->event_received.connect([this](const std::string &event_data) { + std::thread(&sidechain_net_handler_bitcoin::handle_event, this, event_data).detach(); + }); + + database.changed_objects.connect([this](const vector &ids, const flat_set &accounts) { + on_changed_objects(ids, accounts); + }); +} + +sidechain_net_handler_bitcoin::~sidechain_net_handler_bitcoin() { + try { + if (on_changed_objects_task.valid()) { + on_changed_objects_task.cancel_and_wait(__FUNCTION__); + } + } catch (fc::canceled_exception &) { + //Expected exception. Move along. + } catch (fc::exception &e) { + edump((e.to_detail_string())); + } +} + +void sidechain_net_handler_bitcoin::recreate_primary_wallet() { + const auto &swi = database.get_index_type().indices().get(); + const auto &active_sw = swi.rbegin(); + if (active_sw != swi.rend()) { + + if ((active_sw->addresses.find(sidechain_type::bitcoin) == active_sw->addresses.end()) || + (active_sw->addresses.at(sidechain_type::bitcoin).empty())) { + + const chain::global_property_object &gpo = database.get_global_properties(); + + auto active_sons = gpo.active_sons; + vector son_pubkeys_bitcoin; + for (const son_info &si : active_sons) { + son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); + } + + if (!wallet_password.empty()) { + bitcoin_client->walletpassphrase(wallet_password, 5); + } + uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1; + string reply_str = bitcoin_client->addmultisigaddress(nrequired, son_pubkeys_bitcoin); + + std::stringstream active_pw_ss(reply_str); + boost::property_tree::ptree active_pw_pt; + boost::property_tree::read_json(active_pw_ss, active_pw_pt); + if (active_pw_pt.count("error") && active_pw_pt.get_child("error").empty()) { + + std::stringstream res; + boost::property_tree::json_parser::write_json(res, active_pw_pt.get_child("result")); + + son_wallet_update_operation op; + op.payer = gpo.parameters.son_account(); + op.son_wallet_id = active_sw->id; + op.sidechain = sidechain_type::bitcoin; + op.address = res.str(); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for son wallet update operation failed with exception ${e}", ("e", e.what())); + return; + } + + const auto &prev_sw = std::next(active_sw); + if (prev_sw != swi.rend()) { + std::stringstream prev_sw_ss(prev_sw->addresses.at(sidechain_type::bitcoin)); + boost::property_tree::ptree prev_sw_pt; + boost::property_tree::read_json(prev_sw_ss, prev_sw_pt); + + std::string active_pw_address = active_pw_pt.get_child("result").get("address"); + std::string prev_pw_address = prev_sw_pt.get("address"); + + if (prev_pw_address == active_pw_address) { + elog("BTC previous and new primary wallet addresses are same. No funds moving needed [from ${prev_sw} to ${active_sw}]", ("prev_sw", prev_sw->id)("active_sw", active_sw->id)); + return; + } + + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + double min_amount = ((double)fee_rate / 100000000.0); // Account only for relay fee for now + double total_amount = 0.0; + std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(prev_pw_address, 0); + + if (inputs.size() == 0) { + elog("Failed to find UTXOs to spend for ${pw}", ("pw", prev_pw_address)); + return; + } else { + for (const auto &utx : inputs) { + total_amount += utx.amount_; + } + + if (min_amount >= total_amount) { + elog("Failed not enough BTC to transfer from ${fa}", ("fa", prev_pw_address)); + return; + } + } + + fc::flat_map outputs; + outputs[active_pw_address] = total_amount - min_amount; + + std::string tx_str = create_transaction(inputs, outputs); + + if (!tx_str.empty()) { + + auto signer_sons = prev_sw->sons; + std::vector signers; + for (const son_info &si : signer_sons) { + signers.push_back(si.son_id); + } + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = prev_sw->id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = signers; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(stc_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + } catch (fc::exception e) { + elog("Sending proposal for withdrawal sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + } + } + } + } + } + } +} + +bool sidechain_net_handler_bitcoin::process_deposit(const son_wallet_deposit_object &swdo) { + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return false; + } + + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::string pw_address = json.get("address"); + + std::string txid = swdo.sidechain_transaction_id; + std::string suid = swdo.sidechain_uid; + std::string nvout = suid.substr(suid.find_last_of("-") + 1); + uint64_t deposit_amount = swdo.sidechain_amount.value; + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + deposit_amount -= fee_rate; // Deduct minimum relay fee + double transfer_amount = (double)deposit_amount / 100000000.0; + + std::vector inputs; + fc::flat_map outputs; + + btc_txout utxo; + utxo.txid_ = txid; + utxo.out_num_ = std::stoul(nvout); + + inputs.push_back(utxo); + + outputs[pw_address] = transfer_amount; + + std::string tx_str = create_transaction(inputs, outputs); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + auto active_sons = gpo.active_sons; + std::vector signers; + for (const son_info &si : active_sons) { + signers.push_back(si.son_id); + } + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swdo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = signers; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(stc_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for deposit sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +bool sidechain_net_handler_bitcoin::process_withdrawal(const son_wallet_withdraw_object &swwo) { + const auto &idx = database.get_index_type().indices().get(); + auto obj = idx.rbegin(); + if (obj == idx.rend() || obj->addresses.find(sidechain_type::bitcoin) == obj->addresses.end()) { + return false; + } + + std::string pw_address_json = obj->addresses.find(sidechain_type::bitcoin)->second; + + std::stringstream ss(pw_address_json); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + + std::string pw_address = json.get("address"); + + uint64_t fee_rate = bitcoin_client->estimatesmartfee(); + uint64_t min_fee_rate = 1000; + fee_rate = std::max(fee_rate, min_fee_rate); + + double min_amount = ((double)(swwo.withdraw_amount.value + fee_rate) / 100000000.0); // Account only for relay fee for now + double total_amount = 0.0; + std::vector inputs = bitcoin_client->listunspent_by_address_and_amount(pw_address, 0); + + if (inputs.size() == 0) { + elog("Failed to find UTXOs to spend for ${pw}", ("pw", pw_address)); + return ""; + } else { + for (const auto &utx : inputs) { + total_amount += utx.amount_; + } + + if (min_amount > total_amount) { + elog("Failed not enough BTC to spend for ${pw}", ("pw", pw_address)); + return ""; + } + } + + fc::flat_map outputs; + outputs[swwo.withdraw_address] = swwo.withdraw_amount.value / 100000000.0; + if ((total_amount - min_amount) > 0.0) { + outputs[pw_address] = total_amount - min_amount; + } + + std::string tx_str = create_transaction(inputs, outputs); + + if (!tx_str.empty()) { + const chain::global_property_object &gpo = database.get_global_properties(); + + auto active_sons = gpo.active_sons; + std::vector signers; + for (const son_info &si : active_sons) { + signers.push_back(si.son_id); + } + + sidechain_transaction_create_operation stc_op; + stc_op.payer = gpo.parameters.son_account(); + stc_op.object_id = swwo.id; + stc_op.sidechain = sidechain; + stc_op.transaction = tx_str; + stc_op.signers = signers; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = plugin.get_current_son_object().son_account; + proposal_op.proposed_ops.emplace_back(stc_op); + uint32_t lifetime = (gpo.parameters.block_interval * gpo.active_witnesses.size()) * 3; + proposal_op.expiration_time = time_point_sec(database.head_block_time().sec_since_epoch() + lifetime); + + signed_transaction trx = database.create_signed_transaction(plugin.get_private_key(plugin.get_current_son_id()), proposal_op); + trx.validate(); + try { + database.push_transaction(trx, database::validation_steps::skip_block_size_check); + if (plugin.app().p2p_node()) + plugin.app().p2p_node()->broadcast(net::trx_message(trx)); + return true; + } catch (fc::exception e) { + elog("Sending proposal for withdraw sidechain transaction create operation failed with exception ${e}", ("e", e.what())); + return false; + } + } + return false; +} + +std::string sidechain_net_handler_bitcoin::process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete) { + + //// Uncomment to get signing in order from sto.signers + //son_id_type invalid_signer = son_id_type(0xFFFFFFFF); + //son_id_type next_signer = invalid_signer; + //for (auto &signer : sto.signers) { + // if (signer.second == false) { + // next_signer = signer.first; + // break; + // } + //} + // + //if ((next_signer == invalid_signer) || (next_signer != plugin.get_current_son_id())) { + // return ""; + //} + + return sign_transaction(sto.transaction, complete); +} + +bool sidechain_net_handler_bitcoin::send_sidechain_transaction(const sidechain_transaction_object &sto) { + return send_transaction(sto.transaction); +} + +// Creates transaction in any format +// Function to actually create transaction should return transaction string, or empty string in case of failure +std::string sidechain_net_handler_bitcoin::create_transaction(const std::vector &inputs, const fc::flat_map outputs) { + std::string new_tx = ""; + //new_tx = create_transaction_raw(inputs, outputs); + new_tx = create_transaction_psbt(inputs, outputs); + //new_tx = create_transaction_standalone(inputs, outputs); + return new_tx; +} + +// Adds signature to transaction +// Function to actually add signature should return transaction with added signature string, or empty string in case of failure +std::string sidechain_net_handler_bitcoin::sign_transaction(const std::string &tx_str, bool &complete) { + std::string new_tx = ""; + //new_tx = sign_transaction_raw(tx, complete); + new_tx = sign_transaction_psbt(tx_str, complete); + //new_tx = sign_transaction_standalone(tx, complete); + return new_tx; +} + +bool sidechain_net_handler_bitcoin::send_transaction(const std::string &tx_str) { + return bitcoin_client->sendrawtransaction(tx_str); +} + +std::string sidechain_net_handler_bitcoin::create_transaction_raw(const std::vector &inputs, const fc::flat_map outputs) { + return bitcoin_client->createrawtransaction(inputs, outputs); +} + +std::string sidechain_net_handler_bitcoin::create_transaction_psbt(const std::vector &inputs, const fc::flat_map outputs) { + return bitcoin_client->createpsbt(inputs, outputs); +} + +std::string sidechain_net_handler_bitcoin::create_transaction_standalone(const std::vector &inputs, const fc::flat_map outputs) { + // Examples + + // Transaction with no inputs and outputs + //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" createrawtransaction '[]' '[]' + //02000000000000000000 + //bitcoin-core.cli -rpcuser=1 -rpcpassword=1 -rpcwallet="" decoderawtransaction 02000000000000000000 + //{ + // "txid": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", + // "hash": "4ebd325a4b394cff8c57e8317ccf5a8d0e2bdf1b8526f8aad6c8e43d8240621a", + // "version": 2, + // "size": 10, + // "vsize": 10, + // "weight": 40, + // "locktime": 0, + // "vin": [ + // ], + // "vout": [ + // ] + //} + + // Transaction with input and output + //{ + // "txid": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", + // "hash": "ff60f48f767bbf70d79efc1347b5554b481f14fda68709839091286e035e669b", + // "version": 2, + // "size": 83, + // "vsize": 83, + // "weight": 332, + // "locktime": 0, + // "vin": [ + // { + // "txid": "3d322dc2640239a2e68e182b254d19c88e5172a61947f94a105c3f57618092ff", + // "vout": 0, + // "scriptSig": { + // "asm": "", + // "hex": "" + // }, + // "sequence": 4294967295 + // } + // ], + // "vout": [ + // { + // "value": 1.00000000, + // "n": 0, + // "scriptPubKey": { + // "asm": "OP_HASH160 b87c323018cae236eb03a1f63000c85b672270f6 OP_EQUAL", + // "hex": "a914b87c323018cae236eb03a1f63000c85b672270f687", + // "reqSigs": 1, + // "type": "scripthash", + // "addresses": [ + // "2NA4h6sc9oZ4ogfNKU9Wp6fkqPZLZPqqpgf" + // ] + // } + // } + // ] + //} + + return ""; +} + +std::string sidechain_net_handler_bitcoin::sign_transaction_raw(const std::string &tx_str, bool &complete) { + if (tx_str.empty()) { + elog("Signing failed, tx string is empty"); + return ""; + } + + if (!wallet_password.empty()) { + bitcoin_client->walletpassphrase(wallet_password, 5); + } + + std::string reply_str = bitcoin_client->signrawtransactionwithwallet(tx_str); + + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + boost::property_tree::ptree json_res = json.get_child("result"); + + if ((json_res.count("hex") == 0) || (json_res.count("complete") == 0)) { + elog("Failed to process raw transaction ${tx}", ("tx", tx_str)); + return ""; + } + + std::string new_tx_raw = json_res.get("hex"); + bool complete_raw = json_res.get("complete"); + + if (complete_raw) { + complete = true; + return new_tx_raw; + } + return new_tx_raw; +} + +std::string sidechain_net_handler_bitcoin::sign_transaction_psbt(const std::string &tx_str, bool &complete) { + if (tx_str.empty()) { + elog("Signing failed, tx string is empty"); + return ""; + } + + if (!wallet_password.empty()) { + bitcoin_client->walletpassphrase(wallet_password, 5); + } + + std::string reply_str = bitcoin_client->walletprocesspsbt(tx_str); + + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + boost::property_tree::ptree json_res = json.get_child("result"); + + if ((json_res.count("psbt") == 0) || (json_res.count("complete") == 0)) { + elog("Failed to process psbt transaction ${tx}", ("tx", tx_str)); + return ""; + } + + std::string new_tx_psbt = json_res.get("psbt"); + bool complete_psbt = json_res.get("complete"); + + if (complete_psbt) { + std::string reply_str = bitcoin_client->finalizepsbt(new_tx_psbt); + + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json(ss, json); + boost::property_tree::ptree json_res = json.get_child("result"); + + if ((json_res.count("hex") == 0) || (json_res.count("complete") == 0)) { + elog("Failed to finalize psbt transaction ${tx}", ("tx", tx_str)); + return ""; + } + + std::string new_tx_raw = json_res.get("hex"); + bool complete_raw = json_res.get("complete"); + + if (complete_raw) { + complete = true; + return new_tx_raw; + } + } + return new_tx_psbt; +} + +std::string sidechain_net_handler_bitcoin::sign_transaction_standalone(const std::string &tx_str, bool &complete) { + complete = true; + return ""; +} + +void sidechain_net_handler_bitcoin::handle_event(const std::string &event_data) { + std::string block = bitcoin_client->getblock(event_data); + if (block != "") { + const auto &vins = extract_info_from_block(block); + + const auto &sidechain_addresses_idx = database.get_index_type().indices().get(); + + for (const auto &v : vins) { + const auto &addr_itr = sidechain_addresses_idx.find(std::make_tuple(sidechain, v.address)); + if (addr_itr == sidechain_addresses_idx.end()) + continue; + + std::stringstream ss; + ss << "bitcoin" + << "-" << v.out.hash_tx << "-" << v.out.n_vout; + std::string sidechain_uid = ss.str(); + + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.sidechain = addr_itr->sidechain; + sed.sidechain_uid = sidechain_uid; + sed.sidechain_transaction_id = v.out.hash_tx; + sed.sidechain_from = ""; + sed.sidechain_to = v.address; + sed.sidechain_currency = "BTC"; + sed.sidechain_amount = v.out.amount; + sed.peerplays_from = addr_itr->sidechain_address_account; + sed.peerplays_to = database.get_global_properties().parameters.son_account(); + sed.peerplays_asset = asset(sed.sidechain_amount / 1000); // For Bitcoin, the exchange rate is 1:1, for others, get the exchange rate from market + sidechain_event_data_received(sed); + } + } +} + +std::vector sidechain_net_handler_bitcoin::extract_info_from_block(const std::string &_block) { + std::stringstream ss(_block); + boost::property_tree::ptree block; + boost::property_tree::read_json(ss, block); + + std::vector result; + + for (const auto &tx_child : block.get_child("tx")) { + const auto &tx = tx_child.second; + + for (const auto &o : tx.get_child("vout")) { + const auto script = o.second.get_child("scriptPubKey"); + + if (!script.count("addresses")) + continue; + + for (const auto &addr : script.get_child("addresses")) { // in which cases there can be more addresses? + const auto address_base58 = addr.second.get_value(); + info_for_vin vin; + vin.out.hash_tx = tx.get_child("txid").get_value(); + string amount = o.second.get_child("value").get_value(); + amount.erase(std::remove(amount.begin(), amount.end(), '.'), amount.end()); + vin.out.amount = std::stoll(amount); + vin.out.n_vout = o.second.get_child("n").get_value(); + vin.address = address_base58; + result.push_back(vin); + } + } + } + + return result; +} + +void sidechain_net_handler_bitcoin::on_changed_objects(const vector &ids, const flat_set &accounts) { + fc::time_point now = fc::time_point::now(); + int64_t time_to_next_changed_objects_processing = 5000; + + fc::time_point next_wakeup(now + fc::microseconds(time_to_next_changed_objects_processing)); + + on_changed_objects_task = fc::schedule([this, ids, accounts] { + on_changed_objects_cb(ids, accounts); + }, + next_wakeup, "SON Processing"); +} + +void sidechain_net_handler_bitcoin::on_changed_objects_cb(const vector &ids, const flat_set &accounts) { + for (auto id : ids) { + if (id.is()) { + const auto &swi = database.get_index_type().indices().get(); + auto swo = swi.find(id); + if (swo != swi.end()) { + std::stringstream pw_ss(swo->addresses.at(sidechain)); + boost::property_tree::ptree pw_pt; + boost::property_tree::read_json(pw_ss, pw_pt); + + if (!wallet_password.empty()) { + bitcoin_client->walletpassphrase(wallet_password, 5); + } + + std::string pw_address = ""; + if (pw_pt.count("address")) { + pw_address = pw_pt.get("address"); + bitcoin_client->importaddress(pw_address); + } + + std::string pw_redeem_script = ""; + if (pw_pt.count("redeemScript")) { + pw_redeem_script = pw_pt.get("redeemScript"); + bitcoin_client->importaddress(pw_redeem_script); + } + + vector son_pubkeys_bitcoin; + for (const son_info &si : swo->sons) { + son_pubkeys_bitcoin.push_back(si.sidechain_public_keys.at(sidechain_type::bitcoin)); + } + uint32_t nrequired = son_pubkeys_bitcoin.size() * 2 / 3 + 1; + bitcoin_client->addmultisigaddress(nrequired, son_pubkeys_bitcoin); + } + } + } +} + +// ============================================================================= + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp new file mode 100644 index 00000000..8dd39b22 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_handler_peerplays.cpp @@ -0,0 +1,88 @@ +#include + +#include +#include + +#include +#include +#include + +#include +#include +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_handler_peerplays::sidechain_net_handler_peerplays(peerplays_sidechain_plugin &_plugin, const boost::program_options::variables_map &options) : + sidechain_net_handler(_plugin, options) { + sidechain = sidechain_type::peerplays; + database.applied_block.connect([&](const signed_block &b) { + on_applied_block(b); + }); +} + +sidechain_net_handler_peerplays::~sidechain_net_handler_peerplays() { +} + +void sidechain_net_handler_peerplays::recreate_primary_wallet() { + return; +} + +bool sidechain_net_handler_peerplays::process_deposit(const son_wallet_deposit_object &swdo) { + return true; +} + +bool sidechain_net_handler_peerplays::process_withdrawal(const son_wallet_withdraw_object &swwo) { + return true; +} + +std::string sidechain_net_handler_peerplays::process_sidechain_transaction(const sidechain_transaction_object &sto, bool &complete) { + complete = true; + return sto.transaction; +} + +bool sidechain_net_handler_peerplays::send_sidechain_transaction(const sidechain_transaction_object &sto) { + return true; +} + +void sidechain_net_handler_peerplays::on_applied_block(const signed_block &b) { + for (const auto &trx : b.transactions) { + size_t operation_index = -1; + for (auto op : trx.operations) { + operation_index = operation_index + 1; + if (op.which() == operation::tag::value) { + transfer_operation transfer_op = op.get(); + if (transfer_op.to != plugin.database().get_global_properties().parameters.son_account()) { + continue; + } + + std::stringstream ss; + ss << "peerplays" + << "-" << trx.id().str() << "-" << operation_index; + std::string sidechain_uid = ss.str(); + + sidechain_event_data sed; + sed.timestamp = database.head_block_time(); + sed.sidechain = sidechain_type::peerplays; + sed.sidechain_uid = sidechain_uid; + sed.sidechain_transaction_id = trx.id().str(); + sed.sidechain_from = fc::to_string(transfer_op.from.space_id) + "." + fc::to_string(transfer_op.from.type_id) + "." + fc::to_string((uint64_t)transfer_op.from.instance); + sed.sidechain_to = fc::to_string(transfer_op.to.space_id) + "." + fc::to_string(transfer_op.to.type_id) + "." + fc::to_string((uint64_t)transfer_op.to.instance); + sed.sidechain_currency = fc::to_string(transfer_op.amount.asset_id.space_id) + "." + fc::to_string(transfer_op.amount.asset_id.type_id) + "." + fc::to_string((uint64_t)transfer_op.amount.asset_id.instance); //transfer_op.amount.asset_id(database).symbol; + sed.sidechain_amount = transfer_op.amount.amount; + sed.peerplays_from = transfer_op.from; + sed.peerplays_to = transfer_op.to; + // We should calculate exchange rate between CORE/TEST and other Peerplays asset + sed.peerplays_asset = asset(transfer_op.amount.amount / transfer_op.amount.asset_id(database).options.core_exchange_rate.quote.amount); + sidechain_event_data_received(sed); + } + } + } +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp new file mode 100644 index 00000000..826e5d91 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/sidechain_net_manager.cpp @@ -0,0 +1,72 @@ +#include + +#include +#include +#include +#include + +namespace graphene { namespace peerplays_sidechain { + +sidechain_net_manager::sidechain_net_manager(peerplays_sidechain_plugin &_plugin) : + plugin(_plugin), + database(_plugin.database()) { +} + +sidechain_net_manager::~sidechain_net_manager() { +} + +bool sidechain_net_manager::create_handler(sidechain_type sidechain, const boost::program_options::variables_map &options) { + + bool ret_val = false; + + switch (sidechain) { + case sidechain_type::bitcoin: { + std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_bitcoin(plugin, options)); + net_handlers.push_back(std::move(h)); + ret_val = true; + break; + } + case sidechain_type::peerplays: { + std::unique_ptr h = std::unique_ptr(new sidechain_net_handler_peerplays(plugin, options)); + net_handlers.push_back(std::move(h)); + ret_val = true; + break; + } + default: + assert(false); + } + + return ret_val; +} + +void sidechain_net_manager::recreate_primary_wallet() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->recreate_primary_wallet(); + } +} + +void sidechain_net_manager::process_deposits() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_deposits(); + } +} + +void sidechain_net_manager::process_withdrawals() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_withdrawals(); + } +} + +void sidechain_net_manager::process_sidechain_transactions() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->process_sidechain_transactions(); + } +} + +void sidechain_net_manager::send_sidechain_transactions() { + for (size_t i = 0; i < net_handlers.size(); i++) { + net_handlers.at(i)->send_sidechain_transactions(); + } +} + +}} // namespace graphene::peerplays_sidechain diff --git a/libraries/wallet/CMakeLists.txt b/libraries/wallet/CMakeLists.txt index 8c9f8790..382adda1 100644 --- a/libraries/wallet/CMakeLists.txt +++ b/libraries/wallet/CMakeLists.txt @@ -23,7 +23,7 @@ else() endif() add_library( graphene_wallet wallet.cpp ${CMAKE_CURRENT_BINARY_DIR}/api_documentation.cpp ${HEADERS} ) -target_link_libraries( graphene_wallet PRIVATE graphene_app graphene_net graphene_chain graphene_utilities fc ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( graphene_wallet PRIVATE graphene_app graphene_net graphene_chain graphene_utilities fc peerplays_sidechain ${CMAKE_DL_LIBS} ${PLATFORM_SPECIFIC_LIBS} ) target_include_directories( graphene_db PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) if(MSVC) diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index daf3b637..277d1f23 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -1311,6 +1311,7 @@ class wallet_api * display this when showing a list of SONs. May be blank. * @param deposit_id vesting balance id for SON deposit * @param pay_vb_id vesting balance id for SON pay_vb + * @param sidechain_public_keys The new set of sidechain public keys. * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction registering a SON */ @@ -1318,6 +1319,7 @@ class wallet_api string url, vesting_balance_id_type deposit_id, vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, bool broadcast = false); /** @@ -1326,11 +1328,13 @@ class wallet_api * @param witness The name of the SON's owner account. Also accepts the ID of the owner account or the ID of the SON. * @param url Same as for create_son. 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 sidechain_public_keys The new set of sidechain public keys. The empty string makes it remain the same. * @param broadcast true if you wish to broadcast the transaction. */ signed_transaction update_son(string owner_account, string url, string block_signing_key, + flat_map sidechain_public_keys, bool broadcast = false); @@ -1339,14 +1343,30 @@ class wallet_api * An account can have at most one witness object. * * @param owner_account the name or id of the account which is creating the witness - * @param url a URL to include in the witness record in the blockchain. Clients may - * display this when showing a list of witnesses. May be blank. * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction registering a witness */ signed_transaction delete_son(string owner_account, bool broadcast = false); + /** Modify status of the SON owned by the given account to maintenance. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction request_son_maintenance(string owner_account, + bool broadcast = false); + + /** Modify status of the SON owned by the given account back to active. + * + * @param owner_account the name or id of the account which is owning the SON + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction + */ + signed_transaction cancel_request_son_maintenance(string owner_account, + bool broadcast = false); + /** Lists all SONs in the blockchain. * This returns a list of all account names that own SON, and the associated SON id, * sorted by name. This lists SONs whether they are currently voted in or not. @@ -1362,6 +1382,106 @@ class wallet_api */ map list_sons(const string& lowerbound, uint32_t limit); + /** Lists active at the moment SONs. + * This returns a list of all account names that own active SON, and the associated SON id, + * sorted by name. + * @returns a list of active SONs mapping SON names to SON ids + */ + map list_active_sons(); + + /** + * @brief Get active SON wallet + * @return Active SON wallet object + */ + optional get_active_son_wallet(); + + /** + * @brief Get SON wallet that was active for a given time point + * @param time_point Time point + * @return SON wallet object, for the wallet that was active for a given time point + */ + optional get_son_wallet_by_time_point(time_point_sec time_point); + + /** + * @brief Get full list of SON wallets + * @param limit Maximum number of results to return + * @return A list of SON wallet objects + */ + vector> get_son_wallets(uint32_t limit); + + /** Adds sidechain address owned by the given account for a given sidechain. + * + * An account can have at most one sidechain address for one sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain a sidechain to whom address belongs + * @param deposit_address sidechain address for deposits + * @param withdraw_address sidechain address for withdrawals + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction adding sidechain address + */ + signed_transaction add_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast = false); + + /** Updates existing sidechain address owned by the given account for a given sidechain. + * + * Only address, private key and public key might be updated. + * + * @param account the name or id of the account who owns the address + * @param sidechain a sidechain to whom address belongs + * @param deposit_address sidechain address for deposits + * @param withdraw_address sidechain address for withdrawals + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction updating sidechain address + */ + signed_transaction update_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast = false); + + /** Deletes existing sidechain address owned by the given account for a given sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain a sidechain to whom address belongs + * @param broadcast true to broadcast the transaction on the network + * @returns the signed transaction updating sidechain address + */ + signed_transaction delete_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + bool broadcast = false); + + /** Retrieves all sidechain addresses owned by given account. + * + * @param account the name or id of the account who owns the address + * @returns the list of all sidechain addresses owned by given account. + */ + vector> get_sidechain_addresses_by_account(string account); + + /** Retrieves all sidechain addresses registered for a given sidechain. + * + * @param sidechain the name of the sidechain + * @returns the list of all sidechain addresses registered for a given sidechain. + */ + vector> get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain); + + /** Retrieves sidechain address owned by given account for a given sidechain. + * + * @param account the name or id of the account who owns the address + * @param sidechain the name of the sidechain + * @returns the sidechain address owned by given account for a given sidechain. + */ + fc::optional get_sidechain_address_by_account_and_sidechain(string account, peerplays_sidechain::sidechain_type sidechain); + + /** Retrieves the total number of sidechain addresses registered in the system. + * + * @returns the total number of sidechain addresses registered in the system. + */ + uint64_t get_sidechain_addresses_count(); + /** Creates a witness object owned by the given account. * * An account can have at most one witness object. @@ -1428,15 +1548,17 @@ class wallet_api /** Creates a vesting deposit owned by the given account. * - * @param owner_account the name or id of the account - * @param amount the amount to deposit + * @param owner_account vesting balance owner and creator (the name or id) + * @param amount amount to vest + * @param asset_symbol the symbol of the asset to vest * @param vesting_type "normal", "gpos" or "son" * @param broadcast true to broadcast the transaction on the network * @returns the signed transaction registering a vesting object */ - signed_transaction create_vesting(string owner_account, + signed_transaction create_vesting_balance(string owner_account, string amount, - string vesting_type, + string asset_symbol, + vesting_balance_type vesting_type, bool broadcast = false); /** @@ -2113,11 +2235,24 @@ FC_API( graphene::wallet::wallet_api, (update_son) (delete_son) (list_sons) + (list_active_sons) + (request_son_maintenance) + (cancel_request_son_maintenance) + (get_active_son_wallet) + (get_son_wallet_by_time_point) + (get_son_wallets) + (add_sidechain_address) + (update_sidechain_address) + (delete_sidechain_address) + (get_sidechain_addresses_by_account) + (get_sidechain_addresses_by_sidechain) + (get_sidechain_address_by_account_and_sidechain) + (get_sidechain_addresses_count) (create_witness) (update_witness) (create_worker) (update_worker_votes) - (create_vesting) + (create_vesting_balance) (get_vesting_balances) (withdraw_vesting) (vote_for_committee_member) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 97a6723c..2a7a038b 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -73,6 +73,7 @@ #include #include +#include #include #include @@ -822,6 +823,7 @@ public: // account, false otherwise (but it is stored either way) bool import_key(string account_name_or_id, string wif_key) { + fc::scoped_lock lock(_resync_mutex); fc::optional optional_private_key = wif_to_key(wif_key); if (!optional_private_key) FC_THROW("Invalid private key"); @@ -1323,6 +1325,7 @@ public: bool broadcast = false, bool save_wallet = true) { try { + fc::scoped_lock lock(_resync_mutex); int active_key_index = find_first_unused_derived_key_index(owner_privkey); fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index); @@ -1827,8 +1830,10 @@ public: string url, vesting_balance_id_type deposit_id, vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, bool broadcast /* = false */) { try { + fc::scoped_lock lock(_resync_mutex); account_object son_account = get_account(owner_account); auto son_public_key = son_account.active.get_keys()[0]; @@ -1838,6 +1843,7 @@ public: son_create_op.url = url; son_create_op.deposit = deposit_id; son_create_op.pay_vb = pay_vb_id; + son_create_op.sidechain_public_keys = sidechain_public_keys; if (_remote_db->get_son_by_account(son_create_op.owner_account)) FC_THROW("Account ${owner_account} is already a SON", ("owner_account", owner_account)); @@ -1853,6 +1859,7 @@ public: signed_transaction update_son(string owner_account, string url, string block_signing_key, + flat_map sidechain_public_keys, bool broadcast /* = false */) { try { son_object son = get_son(owner_account); @@ -1865,6 +1872,9 @@ public: if( block_signing_key != "" ) { son_update_op.new_signing_key = public_key_type( block_signing_key ); } + if( !sidechain_public_keys.empty() ) { + son_update_op.new_sidechain_public_keys = sidechain_public_keys; + } signed_transaction tx; tx.operations.push_back( son_update_op ); @@ -1892,10 +1902,165 @@ public: return sign_transaction( tx, broadcast ); } FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) } + signed_transaction request_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_maintenance_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + op.request_type = son_maintenance_request_type::request_maintenance; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + + signed_transaction cancel_request_son_maintenance(string owner_account, + bool broadcast) + { try { + son_object son = get_son(owner_account); + + son_maintenance_operation op; + op.owner_account = son.son_account; + op.son_id = son.id; + op.request_type = son_maintenance_request_type::cancel_request_maintenance; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW( (owner_account) ) } + + map list_active_sons() + { try { + global_property_object gpo = get_global_properties(); + vector son_ids; + son_ids.reserve(gpo.active_sons.size()); + std::transform(gpo.active_sons.begin(), gpo.active_sons.end(), + std::inserter(son_ids, son_ids.end()), + [](const son_info& swi) { + return swi.son_id; + }); + std::vector> son_objects = _remote_db->get_sons(son_ids); + vector owners; + for(auto obj: son_objects) + { + if (obj) + owners.push_back(obj->son_account); + } + vector> accs = _remote_db->get_accounts(owners); + std::remove_if(son_objects.begin(), son_objects.end(), + [](const fc::optional& obj) -> bool { return obj.valid(); }); + map result; + std::transform(accs.begin(), accs.end(), son_objects.begin(), + std::inserter(result, result.end()), + [](fc::optional& acct, fc::optional son) { + FC_ASSERT(acct, "Invalid active SONs list in global properties."); + if (son.valid()) + return std::make_pair(string(acct->name), std::move(son->id)); + return std::make_pair(string(acct->name), std::move(son_id_type())); + }); + return result; + } FC_CAPTURE_AND_RETHROW() } + + optional get_active_son_wallet() + { try { + return _remote_db->get_active_son_wallet(); + } FC_CAPTURE_AND_RETHROW() } + + optional get_son_wallet_by_time_point(time_point_sec time_point) + { try { + return _remote_db->get_son_wallet_by_time_point(time_point); + } FC_CAPTURE_AND_RETHROW() } + + vector> get_son_wallets(uint32_t limit) + { try { + return _remote_db->get_son_wallets(limit); + } FC_CAPTURE_AND_RETHROW() } + + signed_transaction add_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast /* = false */) + { try { + account_id_type sidechain_address_account_id = get_account_id(account); + + sidechain_address_add_operation op; + op.sidechain_address_account = sidechain_address_account_id; + op.sidechain = sidechain; + op.deposit_address = deposit_address; + op.withdraw_address = withdraw_address; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW() } + + signed_transaction update_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast /* = false */) + { try { + account_id_type sidechain_address_account_id = get_account_id(account); + fc::optional sao = _remote_db->get_sidechain_address_by_account_and_sidechain(sidechain_address_account_id, sidechain); + if (!sao) + FC_THROW("No sidechain address for account ${account} and sidechain ${sidechain}", ("account", sidechain_address_account_id)("sidechain", sidechain)); + + sidechain_address_update_operation op; + op.sidechain_address_id = sao->id; + op.sidechain_address_account = sidechain_address_account_id; + op.sidechain = sidechain; + op.deposit_address = deposit_address; + op.withdraw_address = withdraw_address; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + } FC_CAPTURE_AND_RETHROW() } + + signed_transaction delete_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + bool broadcast /* = false */) + { try { + account_id_type sidechain_address_account_id = get_account_id(account); + fc::optional sao = _remote_db->get_sidechain_address_by_account_and_sidechain(sidechain_address_account_id, sidechain); + if (!sao) + FC_THROW("No sidechain address for account ${account} and sidechain ${sidechain}", ("account", sidechain_address_account_id)("sidechain", sidechain)); + + sidechain_address_delete_operation op; + op.sidechain_address_id = sao->id; + op.sidechain_address_account = sidechain_address_account_id; + op.sidechain = sidechain; + + signed_transaction tx; + tx.operations.push_back( op ); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction( tx, broadcast ); + + } FC_CAPTURE_AND_RETHROW() } + signed_transaction create_witness(string owner_account, string url, bool broadcast /* = false */) { try { + fc::scoped_lock lock(_resync_mutex); account_object witness_account = get_account(owner_account); fc::ecc::private_key active_private_key = get_private_key_for_account(witness_account); int witness_key_index = find_first_unused_derived_key_index(active_private_key); @@ -2067,27 +2232,21 @@ public: return sign_transaction( tx, broadcast ); } - signed_transaction create_vesting(string owner_account, + signed_transaction create_vesting_balance(string owner_account, string amount, - string vesting_type, + string asset_symbol, + vesting_balance_type vesting_type, bool broadcast /* = false */) { try { account_object son_account = get_account(owner_account); + fc::optional asset_obj = get_asset(asset_symbol); + FC_ASSERT(asset_obj, "Invalid asset symbol {asst}", ("asst", asset_symbol)); vesting_balance_create_operation op; op.creator = son_account.get_id(); op.owner = son_account.get_id(); - op.amount = asset_object().amount_from_string(amount); - if (vesting_type == "normal") - op.balance_type = vesting_balance_type::normal; - else if (vesting_type == "gpos") - op.balance_type = vesting_balance_type::gpos; - else if (vesting_type == "son") - op.balance_type = vesting_balance_type::son; - else - { - FC_ASSERT( false, "unknown vesting type value ${vt}", ("vt", vesting_type) ); - } + op.amount = asset_obj->amount_from_string(amount); + op.balance_type = vesting_type; if (op.balance_type == vesting_balance_type::son) op.policy = dormant_vesting_policy_initializer {}; @@ -2232,26 +2391,27 @@ public: uint16_t desired_number_of_sons, bool broadcast /* = false */) { try { + FC_ASSERT(sons_to_approve.size() || sons_to_reject.size(), "Both accepted and rejected lists can't be empty simultaneously"); account_object voting_account_object = get_account(voting_account); for (const std::string& son : sons_to_approve) { account_id_type son_owner_account_id = get_account_id(son); fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); if (!son_obj) - FC_THROW("Account ${son} is not registered as a witness", ("son", son)); + FC_THROW("Account ${son} is not registered as a SON", ("son", son)); auto insert_result = voting_account_object.options.votes.insert(son_obj->vote_id); if (!insert_result.second) - FC_THROW("Account ${account} was already voting for son ${son}", ("account", voting_account)("son", son)); + FC_THROW("Account ${account} was already voting for SON ${son}", ("account", voting_account)("son", son)); } for (const std::string& son : sons_to_reject) { account_id_type son_owner_account_id = get_account_id(son); fc::optional son_obj = _remote_db->get_son_by_account(son_owner_account_id); if (!son_obj) - FC_THROW("Account ${son} is not registered as a son", ("son", son)); + FC_THROW("Account ${son} is not registered as a SON", ("son", son)); unsigned votes_removed = voting_account_object.options.votes.erase(son_obj->vote_id); if (!votes_removed) - FC_THROW("Account ${account} is already not voting for son ${son}", ("account", voting_account)("son", son)); + FC_THROW("Account ${account} is already not voting for SON ${son}", ("account", voting_account)("son", son)); } voting_account_object.options.num_son = desired_number_of_sons; @@ -4231,29 +4391,32 @@ committee_member_object wallet_api::get_committee_member(string owner_account) return my->get_committee_member(owner_account); } -signed_transaction wallet_api::create_vesting(string owner_account, +signed_transaction wallet_api::create_vesting_balance(string owner_account, string amount, - string vesting_type, + string asset_symbol, + vesting_balance_type vesting_type, bool broadcast /* = false */) { - return my->create_vesting(owner_account, amount, vesting_type, broadcast); + return my->create_vesting_balance(owner_account, amount, asset_symbol, vesting_type, broadcast); } signed_transaction wallet_api::create_son(string owner_account, string url, vesting_balance_id_type deposit_id, vesting_balance_id_type pay_vb_id, + flat_map sidechain_public_keys, bool broadcast /* = false */) { - return my->create_son(owner_account, url, deposit_id, pay_vb_id, broadcast); + return my->create_son(owner_account, url, deposit_id, pay_vb_id, sidechain_public_keys, broadcast); } signed_transaction wallet_api::update_son(string owner_account, string url, string block_signing_key, + flat_map sidechain_public_keys, bool broadcast /* = false */) { - return my->update_son(owner_account, url, block_signing_key, broadcast); + return my->update_son(owner_account, url, block_signing_key, sidechain_public_keys, broadcast); } signed_transaction wallet_api::delete_son(string owner_account, @@ -4262,11 +4425,88 @@ signed_transaction wallet_api::delete_son(string owner_account, return my->delete_son(owner_account, broadcast); } +signed_transaction wallet_api::request_son_maintenance(string owner_account, bool broadcast) +{ + return my->request_son_maintenance(owner_account, broadcast); +} + +signed_transaction wallet_api::cancel_request_son_maintenance(string owner_account, bool broadcast) +{ + return my->cancel_request_son_maintenance(owner_account, broadcast); +} + map wallet_api::list_sons(const string& lowerbound, uint32_t limit) { return my->_remote_db->lookup_son_accounts(lowerbound, limit); } +map wallet_api::list_active_sons() +{ + return my->list_active_sons(); +} + +optional wallet_api::get_active_son_wallet() +{ + return my->get_active_son_wallet(); +} + +optional wallet_api::get_son_wallet_by_time_point(time_point_sec time_point) +{ + return my->get_son_wallet_by_time_point(time_point); +} + +vector> wallet_api::get_son_wallets(uint32_t limit) +{ + return my->get_son_wallets(limit); +} + +signed_transaction wallet_api::add_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast /* = false */) +{ + return my->add_sidechain_address(account, sidechain, deposit_address, withdraw_address, broadcast); +} + +signed_transaction wallet_api::update_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + string deposit_address, + string withdraw_address, + bool broadcast /* = false */) +{ + return my->update_sidechain_address(account, sidechain, deposit_address, withdraw_address, broadcast); +} + +signed_transaction wallet_api::delete_sidechain_address(string account, + peerplays_sidechain::sidechain_type sidechain, + bool broadcast /* = false */) +{ + return my->delete_sidechain_address(account, sidechain, broadcast); +} + +vector> wallet_api::get_sidechain_addresses_by_account(string account) +{ + account_id_type account_id = get_account_id(account); + return my->_remote_db->get_sidechain_addresses_by_account(account_id); +} + +vector> wallet_api::get_sidechain_addresses_by_sidechain(peerplays_sidechain::sidechain_type sidechain) +{ + return my->_remote_db->get_sidechain_addresses_by_sidechain(sidechain); +} + +fc::optional wallet_api::get_sidechain_address_by_account_and_sidechain(string account, peerplays_sidechain::sidechain_type sidechain) +{ + account_id_type account_id = get_account_id(account); + return my->_remote_db->get_sidechain_address_by_account_and_sidechain(account_id, sidechain); +} + +uint64_t wallet_api::get_sidechain_addresses_count() +{ + return my->_remote_db->get_sidechain_addresses_count(); +} + signed_transaction wallet_api::create_witness(string owner_account, string url, bool broadcast /* = false */) diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index a921c0c3..706334eb 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -44,6 +44,11 @@ #include #include #include +#include +#include +#include +#include +#include #include #include diff --git a/programs/witness_node/genesis.json b/programs/witness_node/genesis.json index ab153e7b..1e18d592 100644 --- a/programs/witness_node/genesis.json +++ b/programs/witness_node/genesis.json @@ -352,7 +352,15 @@ "maximum_tournament_start_time_in_future": 2419200, "maximum_tournament_start_delay": 604800, "maximum_tournament_number_of_wins": 100, - "extensions": {} + "extensions": { + "son_vesting_amount": 5000000, + "son_vesting_period": 172800, + "son_pay_daily_max": 20000000, + "son_pay_time": 86400, + "son_deregister_time": 43200, + "son_heartbeat_frequency": 180, + "son_down_time": 360 + } }, "initial_bts_accounts": [], "initial_accounts": [{ @@ -493,4 +501,4 @@ "num_special_accounts": 0, "num_special_assets": 0 } -} \ No newline at end of file +} diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 472da676..b49e089e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -8,7 +8,7 @@ endif() file(GLOB UNIT_TESTS "tests/*.cpp") add_executable( chain_test ${UNIT_TESTS} ${COMMON_SOURCES} ) -target_link_libraries( chain_test graphene_chain graphene_app graphene_account_history graphene_bookie graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( chain_test graphene_chain graphene_app graphene_account_history graphene_bookie peerplays_sidechain graphene_egenesis_none fc graphene_wallet ${PLATFORM_SPECIFIC_LIBS} ) if(MSVC) set_source_files_properties( tests/serialization_tests.cpp PROPERTIES COMPILE_FLAGS "/bigobj" ) endif(MSVC) @@ -23,7 +23,7 @@ target_link_libraries( chain_bench graphene_chain graphene_app graphene_account_ file(GLOB APP_SOURCES "app/*.cpp") add_executable( app_test ${APP_SOURCES} ) -target_link_libraries( app_test graphene_app graphene_account_history graphene_bookie graphene_net graphene_chain graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +target_link_libraries( app_test graphene_app graphene_account_history graphene_witness graphene_bookie graphene_net graphene_chain graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) file(GLOB INTENSE_SOURCES "intense/*.cpp") add_executable( intense_test ${INTENSE_SOURCES} ${COMMON_SOURCES} ) diff --git a/tests/app/main.cpp b/tests/app/main.cpp index 98f19c19..b3775b1f 100644 --- a/tests/app/main.cpp +++ b/tests/app/main.cpp @@ -29,6 +29,8 @@ #include #include +#include +#include #include #include @@ -57,6 +59,8 @@ BOOST_AUTO_TEST_CASE( two_node_network ) graphene::app::application app1; app1.register_plugin(); + app1.register_plugin(); + app1.register_plugin(); boost::program_options::variables_map cfg; cfg.emplace("p2p-endpoint", boost::program_options::variable_value(string("127.0.0.1:0"), false)); app1.initialize(app_dir.path(), cfg); @@ -72,6 +76,8 @@ BOOST_AUTO_TEST_CASE( two_node_network ) graphene::app::application app2; app2.register_plugin(); + app2.register_plugin(); + app2.register_plugin(); cfg2.erase("p2p-endpoint"); cfg2.emplace("p2p-endpoint", boost::program_options::variable_value(string("127.0.0.1:0"), false)); cfg2.emplace("seed-node", boost::program_options::variable_value(vector{endpoint1}, false)); diff --git a/tests/cli/son.cpp b/tests/cli/son.cpp index 1dfd8381..3630000c 100644 --- a/tests/cli/son.cpp +++ b/tests/cli/son.cpp @@ -40,6 +40,7 @@ public: } void create_son(const std::string& account_name, const std::string& son_url, + flat_map& sidechain_public_keys, bool generate_maintenance = true) { graphene::wallet::brain_key_info bki; @@ -78,11 +79,12 @@ public: BOOST_CHECK(fixture_.generate_block()); // create deposit vesting - fixture_.con.wallet_api_ptr->create_vesting(account_name, "50000000", "son", true); + fixture_.con.wallet_api_ptr->create_vesting_balance(account_name, + "50", "1.3.0", vesting_balance_type::son, true); BOOST_CHECK(fixture_.generate_block()); // create pay_vb vesting - fixture_.con.wallet_api_ptr->create_vesting(account_name, "1000000", "normal", true); + fixture_.con.wallet_api_ptr->create_vesting_balance(account_name, "1", "1.3.0", vesting_balance_type::normal, true); BOOST_CHECK(fixture_.generate_block()); // check deposits are here @@ -91,6 +93,7 @@ public: create_tx = fixture_.con.wallet_api_ptr->create_son(account_name, son_url, deposits[0].id, deposits[1].id, + sidechain_public_keys, true); if (generate_maintenance) @@ -109,9 +112,17 @@ BOOST_AUTO_TEST_CASE( create_sons ) BOOST_TEST_MESSAGE("SON cli wallet tests begin"); try { + flat_map sidechain_public_keys; + son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); auto son1_obj = con.wallet_api_ptr->get_son("son1account"); BOOST_CHECK(son1_obj.son_account == con.wallet_api_ptr->get_account_id("son1account")); @@ -135,8 +146,13 @@ BOOST_AUTO_TEST_CASE( cli_update_son ) { BOOST_TEST_MESSAGE("Cli get_son and update_son Test"); + flat_map sidechain_public_keys; + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + son_test_helper sth(*this); - sth.create_son("sonmember", "http://sonmember"); + sth.create_son("sonmember", "http://sonmember", sidechain_public_keys); auto sonmember_acct = con.wallet_api_ptr->get_account("sonmember"); @@ -146,12 +162,16 @@ BOOST_AUTO_TEST_CASE( cli_update_son ) BOOST_CHECK(son_data.son_account == sonmember_acct.get_id()); // update SON - con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated", "", true); + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + + con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated", "", sidechain_public_keys, true); son_data = con.wallet_api_ptr->get_son("sonmember"); BOOST_CHECK(son_data.url == "http://sonmember_updated"); // update SON signing key - con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated2", "TEST6Yaq5ZNTTkMM2kBBzV5jktr8ETsniCC3bnVD7eFmegRrLXfGGG", true); + sidechain_public_keys.clear(); + con.wallet_api_ptr->update_son("sonmember", "http://sonmember_updated2", "TEST6Yaq5ZNTTkMM2kBBzV5jktr8ETsniCC3bnVD7eFmegRrLXfGGG", sidechain_public_keys, true); son_data = con.wallet_api_ptr->get_son("sonmember"); BOOST_CHECK(son_data.url == "http://sonmember_updated2"); BOOST_CHECK(std::string(son_data.signing_key) == "TEST6Yaq5ZNTTkMM2kBBzV5jktr8ETsniCC3bnVD7eFmegRrLXfGGG"); @@ -167,9 +187,17 @@ BOOST_AUTO_TEST_CASE( son_voting ) BOOST_TEST_MESSAGE("SON Vote cli wallet tests begin"); try { + flat_map sidechain_public_keys; + son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); BOOST_TEST_MESSAGE("Voting for SONs"); @@ -238,9 +266,17 @@ BOOST_AUTO_TEST_CASE( delete_son ) BOOST_TEST_MESSAGE("SON delete cli wallet tests begin"); try { - son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); BOOST_TEST_MESSAGE("Deleting SONs"); signed_transaction delete_tx; @@ -278,11 +314,17 @@ BOOST_FIXTURE_TEST_CASE( select_top_fifteen_sons, cli_fixture ) gpo = con.wallet_api_ptr->get_global_properties(); unsigned int son_number = gpo.parameters.maximum_son_count; + flat_map sidechain_public_keys; + // create son accounts for(unsigned int i = 0; i < son_number + 1; i++) { + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); sth.create_son("sonaccount" + fc::to_pretty_string(i), - "http://son" + fc::to_pretty_string(i), false); + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); } BOOST_CHECK(generate_maintenance_block()); @@ -343,9 +385,17 @@ BOOST_AUTO_TEST_CASE( list_son ) BOOST_TEST_MESSAGE("List SONs cli wallet tests begin"); try { + flat_map sidechain_public_keys; + son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); auto res = con.wallet_api_ptr->list_sons("", 100); BOOST_REQUIRE(res.size() == 2); @@ -365,9 +415,17 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) BOOST_TEST_MESSAGE("SON update_son_votes cli wallet tests begin"); try { - son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + flat_map sidechain_public_keys; + + son_test_helper sth(*this); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); BOOST_TEST_MESSAGE("Vote for 2 accounts with update_son_votes"); @@ -392,7 +450,7 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) accepted.push_back("son1account"); accepted.push_back("son2account"); update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 15, true); + rejected, 2, true); BOOST_CHECK(generate_block()); BOOST_CHECK(generate_maintenance_block()); @@ -412,7 +470,7 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) rejected.clear(); rejected.push_back("son1account"); update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 15, true); + rejected, 1, true); BOOST_CHECK(generate_maintenance_block()); // Verify the votes @@ -431,7 +489,7 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) rejected.clear(); rejected.push_back("son1accnt"); BOOST_CHECK_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 15, true), fc::exception); + rejected, 1, true), fc::exception); BOOST_CHECK(generate_block()); // Verify the votes @@ -449,7 +507,7 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) rejected.clear(); rejected.push_back("son2account"); update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 15, true); + rejected, 0, true); BOOST_CHECK(generate_maintenance_block()); // Verify the votes @@ -468,7 +526,24 @@ BOOST_AUTO_TEST_CASE( update_son_votes_test ) rejected.push_back("son1accnt"); accepted.push_back("son1accnt"); BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, - rejected, 15, true), fc::exception); + rejected, 1, true), fc::exception); + BOOST_CHECK(generate_maintenance_block()); + + // Verify the votes + son1_obj = con.wallet_api_ptr->get_son("son1account"); + son1_end_votes = son1_obj.total_votes; + BOOST_CHECK(son1_end_votes == son1_start_votes); + son1_start_votes = son1_end_votes; + son2_obj = con.wallet_api_ptr->get_son("son2account"); + son2_end_votes = son2_obj.total_votes; + BOOST_CHECK(son2_end_votes == son2_start_votes); + son2_start_votes = son2_end_votes; + + // Try to accept and reject empty lists + accepted.clear(); + rejected.clear(); + BOOST_REQUIRE_THROW(update_votes_tx = con.wallet_api_ptr->update_son_votes("nathan", accepted, + rejected, 1, true), fc::exception); BOOST_CHECK(generate_maintenance_block()); // Verify the votes @@ -497,9 +572,17 @@ BOOST_AUTO_TEST_CASE( related_functions ) global_property_object gpo = con.wallet_api_ptr->get_global_properties(); BOOST_CHECK(gpo.active_sons.size() == 0); + flat_map sidechain_public_keys; + son_test_helper sth(*this); - sth.create_son("son1account", "http://son1"); - sth.create_son("son2account", "http://son2"); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 1"; + sth.create_son("son1account", "http://son1", sidechain_public_keys); + + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address 2"; + sth.create_son("son2account", "http://son2", sidechain_public_keys); gpo = con.wallet_api_ptr->get_global_properties(); BOOST_CHECK(gpo.active_sons.size() == 2); @@ -512,6 +595,148 @@ BOOST_AUTO_TEST_CASE( related_functions ) BOOST_TEST_MESSAGE("SON-related functions cli wallet tests end"); } +BOOST_FIXTURE_TEST_CASE( cli_list_active_sons, cli_fixture ) +{ + BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons begin"); + try + { + son_test_helper sth(*this); + + signed_transaction vote_tx; + global_property_object gpo; + + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + vote_tx = con.wallet_api_ptr->vote_for_son(name, name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + for(unsigned int i = 1; i < son_number; i++) + { + std::string name1 = "sonaccount" + fc::to_pretty_string(i); + std::string name2 = "sonaccount" + fc::to_pretty_string(i + 1); + vote_tx = con.wallet_api_ptr->vote_for_son(name1, name2, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + gpo = con.wallet_api_ptr->get_global_properties(); + BOOST_TEST_MESSAGE("gpo: " << gpo.active_sons.size()); + + BOOST_CHECK(gpo.active_sons.size() == son_number); + + map active_sons = con.wallet_api_ptr->list_active_sons(); + BOOST_CHECK(active_sons.size() == son_number); + for(unsigned int i = 1; i < son_number + 1; i++) + { + std::string name = "sonaccount" + fc::to_pretty_string(i); + BOOST_CHECK(active_sons.find(name) != active_sons.end()); + } + + // check list_active_son after SON deletion + con.wallet_api_ptr->delete_son("sonaccount1", true); + BOOST_CHECK_NO_THROW(con.wallet_api_ptr->list_active_sons()); + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON cli wallet tests for list_active_sons end"); +} + +BOOST_AUTO_TEST_CASE( maintenance_test ) +{ + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests begin"); + try + { + son_test_helper sth(*this); + + std::string name("sonaccount1"); + + global_property_object gpo; + gpo = con.wallet_api_ptr->get_global_properties(); + unsigned int son_number = gpo.parameters.maximum_son_count; + + flat_map sidechain_public_keys; + + // create son accounts + for(unsigned int i = 0; i < son_number + 1; i++) + { + sidechain_public_keys.clear(); + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin_address " + fc::to_pretty_string(i); + sth.create_son("sonaccount" + fc::to_pretty_string(i), + "http://son" + fc::to_pretty_string(i), + sidechain_public_keys, + false); + } + BOOST_CHECK(generate_maintenance_block()); + + BOOST_TEST_MESSAGE("Voting for SONs"); + for(unsigned int i = 1; i < son_number + 1; i++) + { + con.wallet_api_ptr->vote_for_son("sonaccount" + fc::to_pretty_string(i), name, true, true); + } + BOOST_CHECK(generate_maintenance_block()); + + son_object son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + // put SON in maintenance mode + con.wallet_api_ptr->request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is in request_maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::request_maintenance); + + // restore SON activity + con.wallet_api_ptr->cancel_request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is active + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::active); + + // put SON in maintenance mode + con.wallet_api_ptr->request_son_maintenance(name, true); + BOOST_CHECK(generate_block()); + + // check SON is in request_maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::request_maintenance); + + // process maintenance + BOOST_CHECK(generate_maintenance_block()); + + // check SON is in maintenance + son_obj = con.wallet_api_ptr->get_son(name); + BOOST_CHECK(son_obj.status == son_status::in_maintenance); + + + } catch( fc::exception& e ) { + BOOST_TEST_MESSAGE("SON cli wallet tests exception"); + edump((e.to_detail_string())); + throw; + } + BOOST_TEST_MESSAGE("SON maintenance cli wallet tests end"); +} BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/common/database_fixture.cpp b/tests/common/database_fixture.cpp index c9917c95..4e171b14 100644 --- a/tests/common/database_fixture.cpp +++ b/tests/common/database_fixture.cpp @@ -51,6 +51,7 @@ #include #include +#include #include #include #include @@ -265,11 +266,11 @@ void database_fixture::verify_asset_supplies( const database& db ) total_balances[betting_market_group.asset_id] += o.fees_collected; } - + uint64_t sweeps_vestings = 0; for( const sweeps_vesting_balance_object& svbo: db.get_index_type< sweeps_vesting_balance_index >().indices() ) sweeps_vestings += svbo.balance; - + total_balances[db.get_global_properties().parameters.sweeps_distribution_asset()] += sweeps_vestings / SWEEPS_VESTING_BALANCE_MULTIPLIER; total_balances[asset_id_type()] += db.get_dynamic_global_properties().witness_budget; total_balances[asset_id_type()] += db.get_dynamic_global_properties().son_budget; @@ -413,6 +414,23 @@ void database_fixture::generate_blocks(fc::time_point_sec timestamp, bool miss_i generate_block(skip); } +bool database_fixture::generate_maintenance_block() { + try { + fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan"))); + uint32_t skip = ~database::skip_fork_db; + auto maint_time = db.get_dynamic_global_properties().next_maintenance_time; + auto slots_to_miss = db.get_slot_at_time(maint_time); + db.generate_block(db.get_slot_time(slots_to_miss), + db.get_scheduled_witness(slots_to_miss), + committee_key, + skip); + return true; + } catch (std::exception& e) + { + return false; + } +} + account_create_operation database_fixture::make_account( const std::string& name /* = "nathan" */, public_key_type key /* = key_id_type() */ @@ -731,7 +749,7 @@ const witness_object& database_fixture::create_witness( const account_object& ow witness_create_operation op; op.witness_account = owner.id; op.block_signing_key = signing_private_key.get_public_key(); - + secret_hash_type::encoder enc; fc::raw::pack(enc, signing_private_key); fc::raw::pack(enc, secret_hash_type()); @@ -1116,12 +1134,12 @@ int64_t database_fixture::get_balance( const account_object& account, const asse } int64_t database_fixture::get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type, - account_id_type dividend_holder_account_id, - asset_id_type dividend_payout_asset_type) const + account_id_type dividend_holder_account_id, + asset_id_type dividend_payout_asset_type) const { - const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = + const pending_dividend_payout_balance_for_holder_object_index& pending_payout_balance_index = db.get_index_type(); - auto pending_payout_iter = + auto pending_payout_iter = pending_payout_balance_index.indices().get().find(boost::make_tuple(dividend_holder_asset_type, dividend_payout_asset_type, dividend_holder_account_id)); if (pending_payout_iter == pending_payout_balance_index.indices().get().end()) return 0; @@ -1342,7 +1360,7 @@ void database_fixture::delete_sport(sport_id_type sport_id) sport_delete_op.sport_id = sport_id; process_operation_by_witnesses(sport_delete_op); } FC_CAPTURE_AND_RETHROW( (sport_id) ) } - + const event_group_object& database_fixture::create_event_group(internationalized_string_type name, sport_id_type sport_id) { try { event_group_create_operation event_group_create_op; @@ -1372,7 +1390,7 @@ void database_fixture::delete_event_group(event_group_id_type event_group_id) process_operation_by_witnesses(event_group_delete_op); } FC_CAPTURE_AND_RETHROW( (event_group_id) ) } - + void database_fixture::try_update_event_group(event_group_id_type event_group_id, fc::optional sport_id, fc::optional name, @@ -1412,7 +1430,7 @@ void database_fixture::update_event_impl(event_id_type event_id, fc::optional event_group_id, fc::optional name, fc::optional season, - fc::optional status, + fc::optional status, bool force) { try { event_update_operation event_update_op; @@ -1449,9 +1467,9 @@ void database_fixture::update_betting_market_rules(betting_market_rules_id_type process_operation_by_witnesses(betting_market_rules_update_op); } FC_CAPTURE_AND_RETHROW( (name)(description) ) } -const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description, - event_id_type event_id, - betting_market_rules_id_type rules_id, +const betting_market_group_object& database_fixture::create_betting_market_group(internationalized_string_type description, + event_id_type event_id, + betting_market_rules_id_type rules_id, asset_id_type asset_id, bool never_in_play, uint32_t delay_before_settling) @@ -1521,7 +1539,7 @@ void database_fixture::update_betting_market(betting_market_id_type betting_mark bet_place_op.amount_to_bet = amount_to_bet; bet_place_op.backer_multiplier = backer_multiplier; bet_place_op.back_or_lay = back_or_lay; - + trx.operations.push_back(bet_place_op); trx.validate(); processed_transaction ptx = db.push_transaction(trx, ~0); diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 200d1897..a190b9c6 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -199,6 +199,12 @@ struct database_fixture { */ void generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks = true, uint32_t skip = ~0); + /////////// + /// @brief Skip intermediate blocks, and generate a maintenance block + /// @returns true on success + /////////// + bool generate_maintenance_block(); + account_create_operation make_account( const std::string& name = "nathan", public_key_type = public_key_type() @@ -295,7 +301,7 @@ struct database_fixture { int64_t get_balance( account_id_type account, asset_id_type a )const; int64_t get_balance( const account_object& account, const asset_object& a )const; int64_t get_dividend_pending_payout_balance(asset_id_type dividend_holder_asset_type, - account_id_type dividend_holder_account_id, + account_id_type dividend_holder_account_id, asset_id_type dividend_payout_asset_type) const; vector< operation_history_object > get_operation_history( account_id_type account_id )const; void process_operation_by_witnesses(operation op); @@ -321,7 +327,7 @@ struct database_fixture { fc::optional season, fc::optional status, bool force); - BOOST_PARAMETER_MEMBER_FUNCTION((void), update_event, keywords::tag, + BOOST_PARAMETER_MEMBER_FUNCTION((void), update_event, keywords::tag, (required (event_id, (event_id_type))) (optional (event_group_id, (fc::optional), fc::optional()) (name, (fc::optional), fc::optional()) @@ -336,9 +342,9 @@ struct database_fixture { void update_betting_market_rules(betting_market_rules_id_type rules_id, fc::optional name, fc::optional description); - const betting_market_group_object& create_betting_market_group(internationalized_string_type description, - event_id_type event_id, - betting_market_rules_id_type rules_id, + const betting_market_group_object& create_betting_market_group(internationalized_string_type description, + event_id_type event_id, + betting_market_rules_id_type rules_id, asset_id_type asset_id, bool never_in_play, uint32_t delay_before_settling); @@ -347,7 +353,7 @@ struct database_fixture { fc::optional rules_id, fc::optional status, bool force); - BOOST_PARAMETER_MEMBER_FUNCTION((void), update_betting_market_group, keywords::tag, + BOOST_PARAMETER_MEMBER_FUNCTION((void), update_betting_market_group, keywords::tag, (required (betting_market_group_id, (betting_market_group_id_type))) (optional (description, (fc::optional), fc::optional()) (rules_id, (fc::optional), fc::optional()) diff --git a/tests/peerplays_sidechain/bitcoin_utils_test.cpp b/tests/peerplays_sidechain/bitcoin_utils_test.cpp new file mode 100644 index 00000000..c0e6e7c1 --- /dev/null +++ b/tests/peerplays_sidechain/bitcoin_utils_test.cpp @@ -0,0 +1,418 @@ +#include +#include +#include +#include +#include +#include + +using namespace graphene::peerplays_sidechain; + +BOOST_AUTO_TEST_CASE(tx_serialization) +{ + // use real mainnet transaction + // txid: 6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315 + // json: {"txid":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","hash":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","version":1,"size":224,"vsize":224,"weight":896,"locktime":0,"vin":[{"txid":"55d079ca797fee81416b71b373abedd8722e33c9f73177be0166b5d5fdac478b","vout":0,"scriptSig":{"asm":"3045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253[ALL] 02be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4","hex":"483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4"},"sequence":4294967295}],"vout":[{"value":1.26491535,"n":0,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 95783804d28e528fbc4b48c7700471e6845804eb OP_EQUALVERIFY OP_CHECKSIG","hex":"76a91495783804d28e528fbc4b48c7700471e6845804eb88ac","reqSigs":1,"type":"pubkeyhash","addresses":["1EdKhXv7zjGowPzgDQ4z1wa2ukVrXRXXkP"]}},{"value":0.0002,"n":1,"scriptPubKey":{"asm":"OP_HASH160 fb0670971091da8248b5c900c6515727a20e8662 OP_EQUAL","hex":"a914fb0670971091da8248b5c900c6515727a20e866287","reqSigs":1,"type":"scripthash","addresses":["3QaKF8zobqcqY8aS6nxCD5ZYdiRfL3RCmU"]}}]} + // hex: "01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000" + fc::string strtx("01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000"); + bytes bintx; + bintx.resize(strtx.length() / 2); + fc::from_hex(strtx, reinterpret_cast(&bintx[0]), bintx.size()); + btc_tx tx; + BOOST_CHECK_NO_THROW(tx.fill_from_bytes(bintx)); + BOOST_CHECK(tx.nVersion == 1); + BOOST_CHECK(tx.nLockTime == 0); + BOOST_CHECK(tx.vin.size() == 1); + BOOST_CHECK(tx.vout.size() == 2); + bytes buff; + tx.to_bytes(buff); + BOOST_CHECK(bintx == buff); +} + +BOOST_AUTO_TEST_CASE(pw_transfer) +{ + // key set for the old Primary Wallet + std::vector priv_old; + for(unsigned i = 0; i < 15; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + // print old keys + for(auto key: priv_old) + { + fc::sha256 secret = key.get_secret(); + bytes data({239}); + data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); + fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); + data.insert(data.end(), cs.data(), cs.data() + 4); + } + std::vector pub_old; + for(auto& key: priv_old) + pub_old.push_back(key.get_public_key()); + // old key weights + std::vector > weights_old; + for(unsigned i = 0; i < 15; ++i) + weights_old.push_back(std::make_pair(pub_old[i], i + 1)); + // redeem script for old PW + bytes redeem_old =generate_redeem_script(weights_old); + + // Old PW address + std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); + // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 + BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); + + bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); + + // key set for the new Primary Wallet + std::vector priv_new; + for(unsigned i = 16; i < 31; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_new.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_new; + for(auto& key: priv_new) + pub_new.push_back(key.get_public_key()); + // new key weights + std::vector > weights_new; + for(unsigned i = 0; i < 15; ++i) + weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); + // redeem script for new PW + bytes redeem_new =generate_redeem_script(weights_new); + // New PW address + std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); + BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); + + // try to move funds from old wallet to new one + + // get unspent outputs for old wallet with list_uspent (address should be + // added to wallet with import_address before). It should return + // 1 UTXO: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766:0] + // with 20000 satoshis + // So, we creating a raw transaction with 1 input and one output that gets + // 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) + // Here we just serialize the transaction without scriptSig in inputs then sign it. + btc_outpoint outpoint; + outpoint.hash = fc::uint256("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); + // reverse hash due to the different from_hex algo + std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); + outpoint.n = 0; + btc_in input; + input.prevout = outpoint; + input.nSequence = 0xffffffff; + btc_out output; + output.nValue = 19000; + output.scriptPubKey = lock_script_for_redeem_script(redeem_new); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = false; + tx.vin.push_back(input); + tx.vout.push_back(output); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + std::vector in_amounts({20000}); + std::vector> keys_to_sign; + for(auto key: priv_old) + keys_to_sign.push_back(fc::optional(key)); + bytes signed_tx =sign_pw_transfer_transaction(unsigned_tx, in_amounts, redeem_old, keys_to_sign); + // this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 + BOOST_CHECK(fc::to_hex((char*)&signed_tx[0], signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); +} + +BOOST_AUTO_TEST_CASE(pw_separate_sign) +{ + // key set for the old Primary Wallet + std::vector priv_old; + for(unsigned i = 0; i < 15; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + // print old keys + for(auto key: priv_old) + { + fc::sha256 secret = key.get_secret(); + bytes data({239}); + data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); + fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); + data.insert(data.end(), cs.data(), cs.data() + 4); + } + std::vector pub_old; + for(auto& key: priv_old) + pub_old.push_back(key.get_public_key()); + // old key weights + std::vector > weights_old; + for(unsigned i = 0; i < 15; ++i) + weights_old.push_back(std::make_pair(pub_old[i], i + 1)); + // redeem script for old PW + bytes redeem_old =generate_redeem_script(weights_old); + + // Old PW address + std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); + // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 + BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); + + bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); + + // key set for the new Primary Wallet + std::vector priv_new; + for(unsigned i = 16; i < 31; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_new.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_new; + for(auto& key: priv_new) + pub_new.push_back(key.get_public_key()); + // new key weights + std::vector > weights_new; + for(unsigned i = 0; i < 15; ++i) + weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); + // redeem script for new PW + bytes redeem_new =generate_redeem_script(weights_new); + // New PW address + std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); + BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); + + // try to move funds from old wallet to new one + + // get unspent outputs for old wallet with list_uspent (address should be + // added to wallet with import_address before). It should return + // 1 UTXO: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766:0] + // with 20000 satoshis + // So, we creating a raw transaction with 1 input and one output that gets + // 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) + // Here we just serialize the transaction without scriptSig in inputs then sign it. + btc_outpoint outpoint; + outpoint.hash = fc::uint256("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); + // reverse hash due to the different from_hex algo + std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); + outpoint.n = 0; + btc_in input; + input.prevout = outpoint; + input.nSequence = 0xffffffff; + btc_out output; + output.nValue = 19000; + output.scriptPubKey = lock_script_for_redeem_script(redeem_new); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = false; + tx.vin.push_back(input); + tx.vout.push_back(output); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + std::vector in_amounts({20000}); + + // prepare tx with dummy signs + bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15); + + // sign with every old key one by one + for(unsigned idx = 0; idx < 15; idx++) + partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx); + + // now this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 + BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); +} + +BOOST_AUTO_TEST_CASE(pw_separate_sign2) +{ + // key set for the old Primary Wallet + std::vector priv_old; + for(unsigned i = 0; i < 15; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + // print old keys + for(auto key: priv_old) + { + fc::sha256 secret = key.get_secret(); + bytes data({239}); + data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); + fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); + data.insert(data.end(), cs.data(), cs.data() + 4); + } + std::vector pub_old; + for(auto& key: priv_old) + pub_old.push_back(key.get_public_key()); + // old key weights + std::vector > weights_old; + for(unsigned i = 0; i < 15; ++i) + weights_old.push_back(std::make_pair(pub_old[i], i + 1)); + // redeem script for old PW + bytes redeem_old =generate_redeem_script(weights_old); + + // Old PW address + std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); + // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 + BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); + + bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); + + // key set for the new Primary Wallet + std::vector priv_new; + for(unsigned i = 16; i < 31; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_new.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_new; + for(auto& key: priv_new) + pub_new.push_back(key.get_public_key()); + // new key weights + std::vector > weights_new; + for(unsigned i = 0; i < 15; ++i) + weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); + // redeem script for new PW + bytes redeem_new =generate_redeem_script(weights_new); + // New PW address + std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); + BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); + + // try to move funds from old wallet to new one + + // get unspent outputs for old wallet with list_uspent (address should be + // added to wallet with import_address before). It should return + // 1 UTXO: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766:0] + // with 20000 satoshis + // So, we creating a raw transaction with 1 input and one output that gets + // 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) + // Here we just serialize the transaction without scriptSig in inputs then sign it. + btc_outpoint outpoint; + outpoint.hash = fc::uint256("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766"); + // reverse hash due to the different from_hex algo + std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); + outpoint.n = 0; + btc_in input; + input.prevout = outpoint; + input.nSequence = 0xffffffff; + btc_out output; + output.nValue = 19000; + output.scriptPubKey = lock_script_for_redeem_script(redeem_new); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = false; + tx.vin.push_back(input); + tx.vout.push_back(output); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + std::vector in_amounts({20000}); + + // gather all signatures from all SONs separatelly + std::vector > signature_set; + for(auto key: priv_old) + { + std::vector signatures = signatures_for_raw_transaction(unsigned_tx, in_amounts, redeem_old, key); + signature_set.push_back(signatures); + } + + // create signed tx with all signatures + bytes signed_tx = add_signatures_to_unsigned_tx(unsigned_tx, signature_set, redeem_old); + + // now this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850 + BOOST_CHECK(fc::to_hex((char*)&signed_tx[0], signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); +} + +BOOST_AUTO_TEST_CASE(pw_partially_sign) +{ + // key set for the old Primary Wallet + std::vector priv_old; + for(unsigned i = 0; i < 15; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_old.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + // print old keys + for(auto key: priv_old) + { + fc::sha256 secret = key.get_secret(); + bytes data({239}); + data.insert(data.end(), secret.data(), secret.data() + secret.data_size()); + fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size())); + data.insert(data.end(), cs.data(), cs.data() + 4); + } + std::vector pub_old; + for(auto& key: priv_old) + pub_old.push_back(key.get_public_key()); + // old key weights + std::vector > weights_old; + for(unsigned i = 0; i < 15; ++i) + weights_old.push_back(std::make_pair(pub_old[i], i + 1)); + // redeem script for old PW + bytes redeem_old =generate_redeem_script(weights_old); + + // Old PW address + std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet); + // This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766 + BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e"); + + bytes scriptPubKey = lock_script_for_redeem_script(redeem_old); + + // key set for the new Primary Wallet + std::vector priv_new; + for(unsigned i = 16; i < 31; ++i) + { + const char* seed = reinterpret_cast(&i); + fc::sha256 h = fc::sha256::hash(seed, sizeof(i)); + priv_new.push_back(fc::ecc::private_key::generate_from_seed(h)); + } + std::vector pub_new; + for(auto& key: priv_new) + pub_new.push_back(key.get_public_key()); + // new key weights + std::vector > weights_new; + for(unsigned i = 0; i < 15; ++i) + weights_new.push_back(std::make_pair(pub_new[i], 16 - i)); + // redeem script for new PW + bytes redeem_new =generate_redeem_script(weights_new); + // New PW address + std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet); + BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y"); + + // try to move funds from old wallet to new one + + // Spent 1 UTXO: [7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf:0] + // with 29999 satoshis + // So, we creating a raw transaction with 1 input and one output that gets + // 29999 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx) + btc_outpoint outpoint; + outpoint.hash = fc::uint256("7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf"); + // reverse hash due to the different from_hex algo + std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size()); + outpoint.n = 0; + btc_in input; + input.prevout = outpoint; + input.nSequence = 0xffffffff; + btc_out output; + output.nValue = 29000; + output.scriptPubKey = lock_script_for_redeem_script(redeem_new); + btc_tx tx; + tx.nVersion = 2; + tx.nLockTime = 0; + tx.hasWitness = false; + tx.vin.push_back(input); + tx.vout.push_back(output); + bytes unsigned_tx; + tx.to_bytes(unsigned_tx); + std::vector in_amounts({29999}); + + // prepare tx with dummy signs + bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15); + + // sign with every old key one by one except the first one + for(unsigned idx = 1; idx < 15; idx++) + partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx); + + // now this is real testnet tx with id e86455c40da6993b6fed70daea2046287b206ab5c16e1ab58c4dfb4a7d6efb84 + BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "02000000000101bf9f1f1bb2658214f45260c020ab080d9012a12a257926d097e05fcd7fb707700000000000ffffffff0148710000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10483045022100c4c567419754c5c1768e959a35633012e8d22ccc90d7cd1b88d6d430a513fbbd0220729c2a3520d0cae7dd6dcd928624ffa3e0b6ce0c4f5c340653a6c18549182588014830450221008c868ea2cdf5b23bdf9e6c7d7c283b8424aeb4aec43621424baef1ee77dd399a02205431f608006f0f0dcd392fab4f25328808b45d4a73852a197e947b289faefece01483045022100aecac85bbb81bc0a4e127c15090c5ab82a62b9e27a9a6eb8eddf8de294aa9d920220482f2ba8d7b62e9f3f7a68b0ef3236bc56e44481d3eb59f62d1daf4b191dc86001483045022100eb27943f8b511a36b1a843f9b3ddf6930aece5a3c0be697dbafc921924fc049c022065ba3e1e4ad57f56337143136c5d3ee3f56dd60f36e798f07b5646e29343d7320147304402206e24158484ebb2cd14b9c410ecd04841d806d8464ce9a827533484c8ad8d921b022021baec9cd0ad46e7b19c8de7df286093b835df5c6243e90b14f5748dc1b7c13901473044022067bfaf0e39d72e49a081d4e43828746ab7524c4764e445173dd96cc7e6187d46022063ef107375cc45d1c26b1e1c87b97694f71645187ad871db9c05b8e981a0da8601483045022100da0162de3e4a5268b616b9d01a1a4f64b0c371c6b44fb1f740a264455f2bc20d02203a0b45a98a341722ad65ae4ad68538d617b1cfbb229751f875615317eaf15dd4014830450221008220c4f97585e67966d4435ad8497eb89945f13dd8ff24048b830582349041a002204cb03f7271895637a31ce6479d15672c2d70528148e3cd6196e6f722117745c50147304402203e83ab4b15bb0680f82779335acf9a3ce45316150a4538d5e3d25cb863fcec5702204b3913874077ed2cae4e10f8786053b6f157973a54d156d5863f13accca595f50147304402201420d2a2830278ffff5842ecb7173a23642f179435443e780b3d1fe04be5c32e02203818202390e0e63b4309b89f9cce08c0f4dfa539c2ed59b05e24325671e2747c0147304402205624ca9d47ae04afd8fff705706d6853f8c679abb385f19e01c36f9380a0bad602203dc817fc55497e4c1759a3dbfff1662faca593a9f10d3a9b3e24d5ee3165d4400147304402203a959f9a34587c56b86826e6ab65644ab19cbd09ca078459eb59956b02bc753002206df5ded568d0e3e3645f8cb8ca02874dd1bfa82933eb5e01ff2e5a773633e51601483045022100a84ed5be60b9e095d40f3f6bd698425cb9c4d8f95e8b43ca6c5120a6c599e9eb022064c703952d18d753f9198d78188a26888e6b06c832d93f8075311d57a13240160147304402202e71d3af33a18397b90072098881fdbdb8d6e4ffa34d21141212dd815c97d00f02207195f1c06a8f44ca72af15fdaba89b07cf6daef9be981c432b9f5c10f1e374200100fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000"); +} diff --git a/tests/tests/history_api_tests.cpp b/tests/tests/history_api_tests.cpp index 0c7d202a..4cbcda89 100644 --- a/tests/tests/history_api_tests.cpp +++ b/tests/tests/history_api_tests.cpp @@ -407,143 +407,143 @@ BOOST_AUTO_TEST_CASE(get_account_history_additional) { } FC_LOG_AND_RETHROW() } -BOOST_AUTO_TEST_CASE(track_account) { - try { - graphene::app::history_api hist_api(app); +//BOOST_AUTO_TEST_CASE(track_account) { +// try { +// graphene::app::history_api hist_api(app); +// +// // account_id_type() is not tracked +// +// // account_id_type() creates alice(not tracked account) +// const account_object& alice = create_account("alice"); +// auto alice_id = alice.id; +// +// //account_id_type() creates some ops +// create_bitasset("CNY", account_id_type()); +// create_bitasset("USD", account_id_type()); +// +// // account_id_type() creates dan(account tracked) +// const account_object& dan = create_account("dan"); +// auto dan_id = dan.id; +// +// // dan makes 1 op +// create_bitasset("EUR", dan_id); +// +// generate_block( ~database::skip_fork_db ); +// +// // anything against account_id_type() should be {} +// vector histories = +// hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 1, operation_history_id_type(2)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// +// // anything against alice should be {} +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(2)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// +// // dan should have history +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 2u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); +// BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); +// +// // create more ops, starting with an untracked account +// create_bitasset( "BTC", account_id_type() ); +// create_bitasset( "GBP", dan_id ); +// +// generate_block( ~database::skip_fork_db ); +// +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 3u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); +// BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); +// BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); +// +// db.pop_block(); +// +// // Try again, should result in same object IDs +// create_bitasset( "BTC", account_id_type() ); +// create_bitasset( "GBP", dan_id ); +// +// generate_block(); +// +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 3u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); +// BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); +// BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); +// } catch (fc::exception &e) { +// edump((e.to_detail_string())); +// throw; +// } +//} - // account_id_type() is not tracked - - // account_id_type() creates alice(not tracked account) - const account_object& alice = create_account("alice"); - auto alice_id = alice.id; - - //account_id_type() creates some ops - create_bitasset("CNY", account_id_type()); - create_bitasset("USD", account_id_type()); - - // account_id_type() creates dan(account tracked) - const account_object& dan = create_account("dan"); - auto dan_id = dan.id; - - // dan makes 1 op - create_bitasset("EUR", dan_id); - - generate_block( ~database::skip_fork_db ); - - // anything against account_id_type() should be {} - vector histories = - hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 1, operation_history_id_type(2)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - - // anything against alice should be {} - histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(2)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - - // dan should have history - histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 2u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); - BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u); - - // create more ops, starting with an untracked account - create_bitasset( "BTC", account_id_type() ); - create_bitasset( "GBP", dan_id ); - - generate_block( ~database::skip_fork_db ); - - histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 3u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); - BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); - BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); - - db.pop_block(); - - // Try again, should result in same object IDs - create_bitasset( "BTC", account_id_type() ); - create_bitasset( "GBP", dan_id ); - - generate_block(); - - histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 3u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u); - BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u); - BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u); - } catch (fc::exception &e) { - edump((e.to_detail_string())); - throw; - } -} - -BOOST_AUTO_TEST_CASE(track_account2) { - try { - graphene::app::history_api hist_api(app); - - // account_id_type() is tracked - - // account_id_type() creates alice(tracked account) - const account_object& alice = create_account("alice"); - auto alice_id = alice.id; - - //account_id_type() creates some ops - create_bitasset("CNY", account_id_type()); - create_bitasset("USD", account_id_type()); - - // alice makes 1 op - create_bitasset("EUR", alice_id); - - // account_id_type() creates dan(account not tracked) - const account_object& dan = create_account("dan"); - auto dan_id = dan.id; - - generate_block(); - - // all account_id_type() should have 4 ops {4,2,1,0} - vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 4u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); - BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u); - BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); - BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); - - // all alice account should have 2 ops {3, 0} - histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 2u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); - BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); - - // alice first op should be {0} - histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 1, operation_history_id_type(1)); - BOOST_CHECK_EQUAL(histories.size(), 1u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); - - // alice second op should be {3} - histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 1u); - BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); - - // anything against dan should be {} - histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 10, operation_history_id_type(0)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 1, operation_history_id_type(2)); - BOOST_CHECK_EQUAL(histories.size(), 0u); - - } catch (fc::exception &e) { - edump((e.to_detail_string())); - throw; - } -} +//BOOST_AUTO_TEST_CASE(track_account2) { +// try { +// graphene::app::history_api hist_api(app); +// +// // account_id_type() is tracked +// +// // account_id_type() creates alice(tracked account) +// const account_object& alice = create_account("alice"); +// auto alice_id = alice.id; +// +// //account_id_type() creates some ops +// create_bitasset("CNY", account_id_type()); +// create_bitasset("USD", account_id_type()); +// +// // alice makes 1 op +// create_bitasset("EUR", alice_id); +// +// // account_id_type() creates dan(account not tracked) +// const account_object& dan = create_account("dan"); +// auto dan_id = dan.id; +// +// generate_block(); +// +// // all account_id_type() should have 4 ops {4,2,1,0} +// vector histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 4u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u); +// BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u); +// BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u); +// BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u); +// +// // all alice account should have 2 ops {3, 0} +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 2u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); +// BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u); +// +// // alice first op should be {0} +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 1, operation_history_id_type(1)); +// BOOST_CHECK_EQUAL(histories.size(), 1u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u); +// +// // alice second op should be {3} +// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 1u); +// BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u); +// +// // anything against dan should be {} +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 10, operation_history_id_type(0)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 1, operation_history_id_type(2)); +// BOOST_CHECK_EQUAL(histories.size(), 0u); +// +// } catch (fc::exception &e) { +// edump((e.to_detail_string())); +// throw; +// } +//} BOOST_AUTO_TEST_CASE(get_account_history_operations) { try { diff --git a/tests/tests/sidechain_addresses_test.cpp b/tests/tests/sidechain_addresses_test.cpp new file mode 100644 index 00000000..e65cf11e --- /dev/null +++ b/tests/tests/sidechain_addresses_test.cpp @@ -0,0 +1,138 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( sidechain_addresses_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( sidechain_address_add_test ) { + + BOOST_TEST_MESSAGE("sidechain_address_add_test"); + + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)); + + generate_block(); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send sidechain_address_add_operation"); + + sidechain_address_add_operation op; + + op.sidechain_address_account = alice_id; + op.sidechain = graphene::peerplays_sidechain::sidechain_type::bitcoin; + op.deposit_address = "deposit_address"; + op.withdraw_address = "withdraw_address"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check sidechain_address_add_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, graphene::peerplays_sidechain::sidechain_type::bitcoin ) ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->sidechain_address_account == alice_id ); + BOOST_CHECK( obj->sidechain == graphene::peerplays_sidechain::sidechain_type::bitcoin ); + BOOST_CHECK( obj->deposit_address == "deposit_address" ); + BOOST_CHECK( obj->withdraw_address == "withdraw_address" ); +} + +BOOST_AUTO_TEST_CASE( sidechain_address_update_test ) { + + BOOST_TEST_MESSAGE("sidechain_address_update_test"); + + INVOKE(sidechain_address_add_test); + + GET_ACTOR(alice); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, graphene::peerplays_sidechain::sidechain_type::bitcoin ) ); + BOOST_REQUIRE( obj != idx.end() ); + + std::string new_deposit_address = "new_deposit_address"; + std::string new_withdraw_address = "new_withdraw_address"; + + { + BOOST_TEST_MESSAGE("Send sidechain_address_update_operation"); + + sidechain_address_update_operation op; + op.sidechain_address_id = sidechain_address_id_type(0); + op.sidechain_address_account = obj->sidechain_address_account; + op.sidechain = obj->sidechain; + op.deposit_address = new_deposit_address; + op.withdraw_address = new_withdraw_address; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check sidechain_address_update_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, graphene::peerplays_sidechain::sidechain_type::bitcoin ) ); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_CHECK( obj->sidechain_address_account == obj->sidechain_address_account ); + BOOST_CHECK( obj->sidechain == obj->sidechain ); + BOOST_CHECK( obj->deposit_address == new_deposit_address ); + BOOST_CHECK( obj->withdraw_address == new_withdraw_address ); + } +} + +BOOST_AUTO_TEST_CASE( sidechain_address_delete_test ) { + + BOOST_TEST_MESSAGE("sidechain_address_delete_test"); + + INVOKE(sidechain_address_add_test); + + GET_ACTOR(alice); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( boost::make_tuple( alice_id, graphene::peerplays_sidechain::sidechain_type::bitcoin ) ); + BOOST_REQUIRE( obj != idx.end() ); + + { + BOOST_TEST_MESSAGE("Send sidechain_address_delete_operation"); + + sidechain_address_delete_operation op; + op.sidechain_address_id = sidechain_address_id_type(0); + op.sidechain_address_account = obj->sidechain_address_account; + op.sidechain = obj->sidechain; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check sidechain_address_delete_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 0 ); + auto obj = idx.find( boost::make_tuple( alice_id, graphene::peerplays_sidechain::sidechain_type::bitcoin ) ); + BOOST_REQUIRE( obj == idx.end() ); + } +} + +BOOST_AUTO_TEST_SUITE_END() + diff --git a/tests/tests/son_operations_tests.cpp b/tests/tests/son_operations_tests.cpp index a458b45e..a917e550 100644 --- a/tests/tests/son_operations_tests.cpp +++ b/tests/tests/son_operations_tests.cpp @@ -83,12 +83,16 @@ BOOST_AUTO_TEST_CASE( create_son_test ) { // alice became son { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address"; + son_create_operation op; op.owner_account = alice_id; op.url = test_url; op.deposit = deposit; op.pay_vb = payment; op.signing_key = alice_public_key; + op.sidechain_public_keys = sidechain_public_keys; trx.operations.push_back(op); sign(trx, alice_private_key); PUSH_TX(db, trx, ~0); @@ -101,6 +105,7 @@ BOOST_AUTO_TEST_CASE( create_son_test ) { BOOST_REQUIRE( obj != idx.end() ); BOOST_CHECK( obj->url == test_url ); BOOST_CHECK( obj->signing_key == alice_public_key ); + BOOST_CHECK( obj->sidechain_public_keys.at(graphene::peerplays_sidechain::sidechain_type::bitcoin) == "bitcoin address" ); BOOST_CHECK( obj->deposit.instance == deposit.instance.value ); BOOST_CHECK( obj->pay_vb.instance == payment.instance.value ); } @@ -113,10 +118,14 @@ BOOST_AUTO_TEST_CASE( update_son_test ) { std::string new_url = "https://anewurl.com"; { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "new bitcoin address"; + son_update_operation op; + op.son_id = son_id_type(0); op.owner_account = alice_id; op.new_url = new_url; - op.son_id = son_id_type(0); + op.new_sidechain_public_keys = sidechain_public_keys; trx.operations.push_back(op); sign(trx, alice_private_key); @@ -129,6 +138,7 @@ BOOST_AUTO_TEST_CASE( update_son_test ) { auto obj = idx.find( alice_id ); BOOST_REQUIRE( obj != idx.end() ); BOOST_CHECK( obj->url == new_url ); + BOOST_CHECK( obj->sidechain_public_keys.at(graphene::peerplays_sidechain::sidechain_type::bitcoin) == "new bitcoin address" ); } BOOST_AUTO_TEST_CASE( delete_son_test ) { @@ -174,6 +184,71 @@ catch (fc::exception &e) { throw; } } +BOOST_AUTO_TEST_CASE( delete_son_test_with_consensus_account ) { +try { + INVOKE(create_son_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + // Modify SON's status to active + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + db.modify( *son_stats_obj, [&]( son_statistics_object& _s) + { + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - db.get_global_properties().parameters.son_deregister_time()); + }); + + auto deposit_vesting = db.get(vesting_balance_id_type(0)); + auto now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // cant withdraw + + { + trx.clear(); + son_delete_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.payer = db.get_global_properties().parameters.son_account(); + + trx.operations.push_back(op); + sign(trx, bob_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_REQUIRE( idx.size() == 0 ); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + BOOST_CHECK_EQUAL(deposit_vesting.policy.get().vesting_cliff_seconds, + db.get_global_properties().parameters.son_vesting_period()); // in linear policy + + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), false); // but still cant withdraw + + generate_blocks(now + fc::seconds(db.get_global_properties().parameters.son_vesting_period())); + generate_block(); + + deposit_vesting = db.get(vesting_balance_id_type(0)); + now = db.head_block_time(); + BOOST_CHECK_EQUAL(deposit_vesting.is_withdraw_allowed(now, asset(50)), true); // after 2 days withdraw is allowed +} +catch (fc::exception &e) { + edump((e.to_detail_string())); + throw; +} } + BOOST_AUTO_TEST_CASE( update_delete_not_own ) { // fee payer needs to be the son object owner try { @@ -335,7 +410,7 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); op.balance_type = vesting_balance_type::son; op.policy = dormant_vesting_policy_initializer {}; - + trx.operations.push_back(op); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); set_expiration(db, trx); @@ -352,7 +427,7 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) op.owner = bob_id; op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); op.balance_type = vesting_balance_type::normal; - + trx.operations.push_back(op); for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); set_expiration(db, trx); @@ -438,6 +513,9 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) // Check if the signed transaction statistics are reset for both SONs BOOST_REQUIRE_EQUAL(son_stats_obj1->txs_signed, 0); BOOST_REQUIRE_EQUAL(son_stats_obj2->txs_signed, 0); + + BOOST_REQUIRE_EQUAL(son_stats_obj1->total_txs_signed, 2); + BOOST_REQUIRE_EQUAL(son_stats_obj2->total_txs_signed, 3); // Check that Alice and Bob are paid for signing the transactions in the previous day/cycle BOOST_REQUIRE_EQUAL(db.get_balance(obj1->son_account, asset_id_type()).amount.value, 80+obj1_balance); BOOST_REQUIRE_EQUAL(db.get_balance(obj2->son_account, asset_id_type()).amount.value, 120+obj2_balance); @@ -448,208 +526,273 @@ BOOST_AUTO_TEST_CASE( son_pay_test ) } -BOOST_AUTO_TEST_CASE( son_witness_proposal_test ) -{ +BOOST_AUTO_TEST_CASE( son_heartbeat_test ) { + try { - const dynamic_global_property_object& dpo = db.get_dynamic_global_properties(); - generate_blocks(HARDFORK_SON_TIME); - generate_block(); - generate_block(); - set_expiration(db, trx); + INVOKE(create_son_test); + GET_ACTOR(alice); - ACTORS((alice)(bob)); - - upgrade_to_lifetime_member(alice); - upgrade_to_lifetime_member(bob); - - transfer( committee_account, alice_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); - transfer( committee_account, bob_id, asset( 1000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); - - set_expiration(db, trx); - generate_block(); - // Now create SONs - std::string test_url1 = "https://create_son_test1"; - std::string test_url2 = "https://create_son_test2"; - - // create deposit vesting - vesting_balance_id_type deposit1; { - vesting_balance_create_operation op; - op.creator = alice_id; - op.owner = alice_id; - op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::son; - op.policy = dormant_vesting_policy_initializer {}; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - deposit1 = ptx.operation_results[0].get(); - } - - // create payment vesting - vesting_balance_id_type payment1; - { - vesting_balance_create_operation op; - op.creator = alice_id; - op.owner = alice_id; - op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::normal; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - payment1 = ptx.operation_results[0].get(); - } - - // create deposit vesting - vesting_balance_id_type deposit2; - { - vesting_balance_create_operation op; - op.creator = bob_id; - op.owner = bob_id; - op.amount = asset(50*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::son; - op.policy = dormant_vesting_policy_initializer {}; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - deposit2 = ptx.operation_results[0].get(); - } - - // create payment vesting - vesting_balance_id_type payment2; - { - vesting_balance_create_operation op; - op.creator = bob_id; - op.owner = bob_id; - op.amount = asset(1*GRAPHENE_BLOCKCHAIN_PRECISION); - op.balance_type = vesting_balance_type::normal; - - trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - set_expiration(db, trx); - processed_transaction ptx = PUSH_TX(db, trx, ~0); - trx.clear(); - payment2 = ptx.operation_results[0].get(); - } - - // alice becomes son - { - son_create_operation op; + // Send Heartbeat for an inactive SON + son_heartbeat_operation op; op.owner_account = alice_id; - op.url = test_url1; - op.deposit = deposit1; - op.pay_vb = payment1; - op.fee = asset(0); - op.signing_key = alice_public_key; + op.son_id = son_id_type(0); + op.ts = fc::time_point::now(); + trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); + set_expiration(db, trx); sign(trx, alice_private_key); - PUSH_TX(db, trx, ~0); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); trx.clear(); } - // bob becomes son { - son_create_operation op; - op.owner_account = bob_id; - op.url = test_url2; - op.deposit = deposit2; - op.pay_vb = payment2; - op.fee = asset(0); - op.signing_key = bob_public_key; + // Try to go in maintenance for an inactive SON + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::request_maintenance; + trx.operations.push_back(op); - for( auto& op : trx.operations ) db.current_fee_schedule().set_fee(op); - sign(trx, bob_private_key); - PUSH_TX(db, trx, ~0); + set_expiration(db, trx); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); trx.clear(); } - generate_block(); - // Check if SONs are created properly + const auto& idx = db.get_index_type().indices().get(); - BOOST_REQUIRE( idx.size() == 2 ); - // Alice's SON - auto obj1 = idx.find( alice_id ); - BOOST_REQUIRE( obj1 != idx.end() ); - BOOST_CHECK( obj1->url == test_url1 ); - BOOST_CHECK( obj1->signing_key == alice_public_key ); - BOOST_CHECK( obj1->deposit.instance == deposit1.instance.value ); - BOOST_CHECK( obj1->pay_vb.instance == payment1.instance.value ); - // Bob's SON - auto obj2 = idx.find( bob_id ); - BOOST_REQUIRE( obj2 != idx.end() ); - BOOST_CHECK( obj2->url == test_url2 ); - BOOST_CHECK( obj2->signing_key == bob_public_key ); - BOOST_CHECK( obj2->deposit.instance == deposit2.instance.value ); - BOOST_CHECK( obj2->pay_vb.instance == payment2.instance.value ); - // Get the statistics object for the SONs + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + const auto& sidx = db.get_index_type().indices().get(); - BOOST_REQUIRE( sidx.size() == 2 ); - auto son_stats_obj1 = sidx.find( obj1->statistics ); - auto son_stats_obj2 = sidx.find( obj2->statistics ); - BOOST_REQUIRE( son_stats_obj1 != sidx.end() ); - BOOST_REQUIRE( son_stats_obj2 != sidx.end() ); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); - - // Modify SON's status to in_maintenance - db.modify( *obj1, [&]( son_object& _s) + // Modify SON's status to active + db.modify( *obj, [&]( son_object& _s) { - _s.status = son_status::in_maintenance; + _s.status = son_status::active; }); - // Modify the Alice's SON down timestamp to now-12 hours - db.modify( *son_stats_obj1, [&]( son_statistics_object& _s) + db.modify( *son_stats_obj, [&]( son_statistics_object& _s) { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - fc::hours(12)); + _s.last_down_timestamp = fc::time_point_sec(db.head_block_time()); }); - // Modify SON's status to in_maintenance - db.modify( *obj2, [&]( son_object& _s) - { - _s.status = son_status::in_maintenance; - }); - - // Modify the Bob's SON down timestamp to now-12 hours - db.modify( *son_stats_obj2, [&]( son_statistics_object& _s) - { - _s.last_down_timestamp = fc::time_point_sec(db.head_block_time() - fc::hours(12)); - }); - - const auto& son_proposal_idx = db.get_index_type().indices().get(); - const auto& proposal_idx = db.get_index_type().indices().get(); - - BOOST_CHECK( son_proposal_idx.size() == 0 && proposal_idx.size() == 0 ); - - generate_block(); - witness_id_type proposal_initiator = dpo.current_witness; - - BOOST_CHECK( son_proposal_idx.size() == 2 && proposal_idx.size() == 2 ); - - for(size_t i = 0 ; i < 3 * db.get_global_properties().active_witnesses.size() ; i++ ) { generate_block(); - if( dpo.current_witness != proposal_initiator) - { - BOOST_CHECK( son_proposal_idx.size() == 2 && proposal_idx.size() == 2 ); - } - else - { - break; - } - } - BOOST_CHECK( son_proposal_idx.size() == 0 && proposal_idx.size() == 0 ); - BOOST_REQUIRE( idx.size() == 0 ); - generate_block(); - } FC_LOG_AND_RETHROW() + // Request SON Maintenance + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::request_maintenance; + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_CHECK( obj->status == son_status::request_maintenance); + } + + { + generate_block(); + // Cancel SON Maintenance request + son_maintenance_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.request_type = son_maintenance_request_type::cancel_request_maintenance; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_CHECK( obj->status == son_status::active); + } + + // Modify SON's status to in_maintenance + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + uint64_t downtime = 0; + + { + generate_block(); + // Send Heartbeat for an in_maintenance SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + BOOST_CHECK( obj->status == son_status::inactive); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + + // Modify SON's status to in_maintenance + db.modify( *obj, [&]( son_object& _s) + { + _s.status = son_status::in_maintenance; + }); + + // SON is selected as one of the active SONs + db.modify( db.get_global_properties(), [&]( global_property_object& _gpo ) + { + son_info son_inf; + son_inf.son_id = son_id_type(0); + _gpo.active_sons.push_back(son_inf); + }); + + { + generate_block(); + // Send Heartbeat for an in_maintenance SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime + op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch()); + downtime += op.ts.sec_since_epoch() - son_stats_obj->last_down_timestamp.sec_since_epoch(); + BOOST_CHECK( obj->status == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + + { + generate_block(); + // Send Heartbeat for an active SON + son_heartbeat_operation op; + op.owner_account = alice_id; + op.son_id = son_id_type(0); + op.ts = (db.head_block_time()+fc::seconds(2*db.block_interval())); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + BOOST_REQUIRE_EQUAL(son_stats_obj->current_interval_downtime, downtime); + BOOST_CHECK( obj->status == son_status::active); + BOOST_CHECK( son_stats_obj->last_active_timestamp == op.ts); + } + } FC_LOG_AND_RETHROW() +} + +BOOST_AUTO_TEST_CASE( son_report_down_test ) { + + try + { + INVOKE(son_heartbeat_test); + GET_ACTOR(alice); + GET_ACTOR(bob); + + generate_block(); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find( alice_id ); + BOOST_REQUIRE( obj != idx.end() ); + + const auto& sidx = db.get_index_type().indices().get(); + BOOST_REQUIRE( sidx.size() == 1 ); + auto son_stats_obj = sidx.find( obj->statistics ); + BOOST_REQUIRE( son_stats_obj != sidx.end() ); + + BOOST_CHECK( obj->status == son_status::active); + + { + // Check that transaction fails if down_ts < last_active_timestamp + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = fc::time_point_sec(son_stats_obj->last_active_timestamp - fc::seconds(1)); + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction fails if payer is not db.get_global_properties().parameters.son_account(). + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = alice_id; + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, alice_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + + { + // Check that transaction succeeds after getting enough approvals on db.get_global_properties().parameters.son_account(). + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + PUSH_TX( db, trx, ~0); + generate_block(); + trx.clear(); + + BOOST_CHECK( obj->status == son_status::in_maintenance); + BOOST_CHECK( son_stats_obj->last_down_timestamp == op.down_ts); + } + + { + // Check that transaction fails if report down sent for an in_maintenance SON. + generate_block(); + // Send Report Down Operation for an active status SON + son_report_down_operation op; + op.payer = db.get_global_properties().parameters.son_account(); + op.son_id = son_id_type(0); + op.down_ts = son_stats_obj->last_active_timestamp; + + trx.operations.push_back(op); + set_expiration(db, trx); + sign(trx, bob_private_key); + // Expect an exception + GRAPHENE_REQUIRE_THROW(PUSH_TX( db, trx, ~0), fc::exception); + trx.clear(); + } + } FC_LOG_AND_RETHROW() } BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/son_wallet_tests.cpp b/tests/tests/son_wallet_tests.cpp new file mode 100644 index 00000000..7ef3abc0 --- /dev/null +++ b/tests/tests/son_wallet_tests.cpp @@ -0,0 +1,225 @@ +#include + +#include "../common/database_fixture.hpp" + +#include +#include +#include + +using namespace graphene; +using namespace graphene::chain; +using namespace graphene::chain::test; + +BOOST_FIXTURE_TEST_SUITE( son_wallet_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( son_wallet_recreate_test ) { + + BOOST_TEST_MESSAGE("son_wallet_recreate_test"); + + generate_blocks(HARDFORK_SON_TIME); + generate_block(); + set_expiration(db, trx); + + ACTORS((alice)(bob)); + + upgrade_to_lifetime_member(alice); + upgrade_to_lifetime_member(bob); + + transfer( committee_account, alice_id, asset( 500000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + transfer( committee_account, bob_id, asset( 500000*GRAPHENE_BLOCKCHAIN_PRECISION ) ); + + generate_block(); + set_expiration(db, trx); + + std::string test_url = "https://create_son_test"; + + // create deposit vesting + vesting_balance_id_type deposit_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_alice = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_alice; + { + vesting_balance_create_operation op; + op.creator = alice_id; + op.owner = alice_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, alice_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_alice = ptx.operation_results[0].get(); + } + + generate_block(); + set_expiration(db, trx); + + // alice becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = alice_id; + op.url = test_url; + op.deposit = deposit_alice; + op.pay_vb = payment_alice; + op.signing_key = alice_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + set_expiration(db, trx); + + // create deposit vesting + vesting_balance_id_type deposit_bob; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::son; + op.policy = dormant_vesting_policy_initializer {}; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + deposit_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // create payment normal vesting + vesting_balance_id_type payment_bob ; + { + vesting_balance_create_operation op; + op.creator = bob_id; + op.owner = bob_id; + op.amount = asset(500 * GRAPHENE_BLOCKCHAIN_PRECISION); + op.balance_type = vesting_balance_type::normal; + trx.operations.push_back(op); + sign(trx, bob_private_key); + processed_transaction ptx = PUSH_TX(db, trx, ~0); + trx.clear(); + payment_bob = ptx.operation_results[0].get(); + } + generate_block(); + set_expiration(db, trx); + + // bob becomes son + { + flat_map sidechain_public_keys; + sidechain_public_keys[graphene::peerplays_sidechain::sidechain_type::bitcoin] = "bitcoin address"; + + son_create_operation op; + op.owner_account = bob_id; + op.url = test_url; + op.deposit = deposit_bob; + op.pay_vb = payment_bob; + op.signing_key = bob_public_key; + op.sidechain_public_keys = sidechain_public_keys; + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + trx.clear(); + } + generate_block(); + set_expiration(db, trx); + + generate_blocks(60); + set_expiration(db, trx); + + { + BOOST_TEST_MESSAGE("Send son_wallet_recreate_operation"); + + son_wallet_recreate_operation op; + + op.payer = db.get_global_properties().parameters.son_account(); + + { + son_info si; + si.son_id = son_id_type(0); + si.total_votes = 1000; + si.signing_key = alice_public_key; + si.sidechain_public_keys[peerplays_sidechain::sidechain_type::bitcoin] = ""; + op.sons.push_back(si); + } + + { + son_info si; + si.son_id = son_id_type(1); + si.total_votes = 1000; + si.signing_key = bob_public_key; + si.sidechain_public_keys[peerplays_sidechain::sidechain_type::bitcoin] = ""; + op.sons.push_back(si); + } + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + BOOST_TEST_MESSAGE("Check son_wallet_recreate_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find(son_wallet_id_type(0)); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_REQUIRE( obj->expires == time_point_sec::maximum() ); +} + +BOOST_AUTO_TEST_CASE( son_wallet_update_test ) { + + BOOST_TEST_MESSAGE("son_wallet_update_test"); + + INVOKE(son_wallet_recreate_test); + GET_ACTOR(alice); + + { + BOOST_TEST_MESSAGE("Send son_wallet_update_operation"); + + son_wallet_update_operation op; + + op.payer = db.get_global_properties().parameters.son_account(); + op.son_wallet_id = son_wallet_id_type(0); + op.sidechain = graphene::peerplays_sidechain::sidechain_type::bitcoin; + op.address = "bitcoin address"; + + trx.operations.push_back(op); + sign(trx, alice_private_key); + PUSH_TX(db, trx, ~0); + } + generate_block(); + + { + BOOST_TEST_MESSAGE("Check son_wallet_update_operation results"); + + const auto& idx = db.get_index_type().indices().get(); + BOOST_REQUIRE( idx.size() == 1 ); + auto obj = idx.find(son_wallet_id_type(0)); + BOOST_REQUIRE( obj != idx.end() ); + BOOST_REQUIRE( obj->addresses.at(graphene::peerplays_sidechain::sidechain_type::bitcoin) == "bitcoin address" ); + } + +} + +BOOST_AUTO_TEST_SUITE_END()