diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 7e4ff990..f0ee0e2d 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -298,6 +298,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.payer ); } + void operator()( const bitcoin_issue_operation& op ) + { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index 4fae0fd2..59c93fd2 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -88,6 +88,7 @@ add_library( graphene_chain buyback.cpp bitcoin_address_evaluator.cpp bitcoin_transaction_evaluator.cpp + sidechain_evaluator.cpp account_object.cpp asset_object.cpp diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d21fbec0..6009314f 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -85,6 +85,7 @@ #include #include #include +#include #include @@ -251,6 +252,7 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); } void database::initialize_indexes() diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 023d4bd7..1baa8ffc 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -285,6 +285,10 @@ struct get_impacted_account_visitor { _impacted.insert( op.payer ); } + void operator()( const bitcoin_issue_operation& op ) + { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp b/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp index eb4beea7..d2961f2e 100644 --- a/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp +++ b/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp @@ -14,17 +14,17 @@ class bitcoin_transaction_object : public abstract_object pw_vin; + fc::sha256 pw_vin; std::vector< info_for_used_vin_id_type > vins; - std::vector< info_for_vout_id_type > vouts; + std::vector< info_for_vout_id_type > vouts; - sidechain::bitcoin_transaction transaction; - fc::sha256 transaction_id; + sidechain::bitcoin_transaction transaction; + fc::sha256 transaction_id; - uint64_t fee_for_size; + uint64_t fee_for_size; - bool confirm = false; + bool confirm = false; }; struct by_transaction_id; diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index dcd2f01c..b1496973 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -47,6 +47,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -136,7 +137,8 @@ namespace graphene { namespace chain { withdraw_pbtc_operation, bitcoin_address_create_operation, bitcoin_transaction_send_operation, - bitcoin_transaction_sign_operation + bitcoin_transaction_sign_operation, + bitcoin_issue_operation > operation; /// @} // operations group diff --git a/libraries/chain/include/graphene/chain/protocol/sidechain.hpp b/libraries/chain/include/graphene/chain/protocol/sidechain.hpp new file mode 100644 index 00000000..fe077f40 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/sidechain.hpp @@ -0,0 +1,28 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct bitcoin_issue_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + }; + + asset fee; + account_id_type payer; + fc::sha256 transaction_id; + + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + share_type fee_required = k.fee; + return fee_required; + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_issue_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_issue_operation, (fee)(payer)(transaction_id) ) diff --git a/libraries/chain/include/graphene/chain/sidechain_evaluator.hpp b/libraries/chain/include/graphene/chain/sidechain_evaluator.hpp new file mode 100644 index 00000000..8b61fc1d --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_evaluator.hpp @@ -0,0 +1,26 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +struct bitcoin_issue_evaluator : public evaluator< bitcoin_issue_evaluator > +{ + typedef bitcoin_issue_operation operation_type; + + void_result do_evaluate( const bitcoin_issue_operation& op ); + + void_result do_apply( const bitcoin_issue_operation& op ); + + void add_issue( const bitcoin_transaction_object& btc_obj ); + + void clear_btc_transaction_information( const bitcoin_transaction_object& btc_obj ); + + std::vector get_amounts_to_issue( std::vector vins_id ); + + std::vector get_accounts_to_issue( std::vector vins_id ); +}; + +} } // graphene::chain \ No newline at end of file diff --git a/libraries/chain/sidechain_evaluator.cpp b/libraries/chain/sidechain_evaluator.cpp new file mode 100644 index 00000000..e04fb285 --- /dev/null +++ b/libraries/chain/sidechain_evaluator.cpp @@ -0,0 +1,134 @@ +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result bitcoin_issue_evaluator::do_evaluate( const bitcoin_issue_operation& op ) +{ + database& d = db(); + + const auto& btc_trx_idx = d.get_index_type().indices().get(); + const auto& btc_addr_idx = d.get_index_type().indices().get(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + const auto& vouts_info_idx = d.get_index_type().indices().get(); + + FC_ASSERT( op.payer == db().get_sidechain_account_id() ); + + const auto& btc_itr = btc_trx_idx.find( op.transaction_id ); + FC_ASSERT( btc_itr != btc_trx_idx.end() ); + + for( auto& vin_id : btc_itr->vins ){ + FC_ASSERT( vins_info_idx.find( vin_id ) != vins_info_idx.end() ); + + auto addr_itr = btc_addr_idx.find( vins_info_idx.find( vin_id )->address ); + FC_ASSERT( addr_itr != btc_addr_idx.end() ); + } + + for( auto& vout_id : btc_itr->vouts ) + FC_ASSERT( vouts_info_idx.find( vout_id ) != vouts_info_idx.end() ); + + return void_result(); +} + +void_result bitcoin_issue_evaluator::do_apply( const bitcoin_issue_operation& op ) +{ + database& d = db(); + const auto& btc_trx_idx = d.get_index_type().indices().get(); + const auto& btc_obj = *btc_trx_idx.find( op.transaction_id ); + + add_issue( btc_obj ); + + d.pw_vout_manager.confirm_vout( btc_obj.pw_vin ); + + clear_btc_transaction_information( btc_obj ); + + return void_result(); +} + +void bitcoin_issue_evaluator::add_issue( const bitcoin_transaction_object& btc_obj ) +{ + database& d = db(); + + const auto& accounts_to_issue = get_accounts_to_issue( btc_obj.vins ); + const auto& amounts_to_issue = get_amounts_to_issue( btc_obj.vins ); + + bool skip_fee_old = trx_state->skip_fee; + bool skip_fee_schedule_check_old = trx_state->skip_fee_schedule_check; + trx_state->skip_fee = true; + trx_state->skip_fee_schedule_check = true; + + for( size_t i = 0; i < accounts_to_issue.size(); i++ ){ + asset_issue_operation issue_op; + issue_op.issuer = d.get_sidechain_account_id(); + issue_op.asset_to_issue = asset( amounts_to_issue[i], d.get_sidechain_asset_id() ); + issue_op.issue_to_account = accounts_to_issue[i]; + + d.apply_operation( *trx_state, issue_op ); + } + + trx_state->skip_fee = skip_fee_old; + trx_state->skip_fee_schedule_check = skip_fee_schedule_check_old; +} + +void bitcoin_issue_evaluator::clear_btc_transaction_information( const bitcoin_transaction_object& btc_obj ) +{ + database& d = db(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + const auto& vouts_info_idx = d.get_index_type().indices().get(); + + for( auto& vin_id : btc_obj.vins ) { + auto vin_itr = vins_info_idx.find( vin_id ); + d.remove( *vin_itr ); + } + + for( auto& vout_id : btc_obj.vouts ) { + auto vout_itr = vouts_info_idx.find( vout_id ); + d.remove( *vout_itr ); + } + + auto trx_approvals = d.bitcoin_confirmations.find( btc_obj.transaction_id ); + if( trx_approvals.valid() ) { + d.bitcoin_confirmations.remove( btc_obj.transaction_id ); + } + + d.remove( btc_obj ); +} + +std::vector bitcoin_issue_evaluator::get_amounts_to_issue( std::vector vins_id ) +{ + database& d = db(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + + std::vector result; + + for( auto& id : vins_id ) { + auto vin_itr = vins_info_idx.find( id ); + result.push_back( vin_itr->out.amount ); + } + + return result; +} + +std::vector bitcoin_issue_evaluator::get_accounts_to_issue( std::vector vins_id ) +{ + database& d = db(); + const auto& btc_addr_idx = d.get_index_type().indices().get(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + + std::vector result; + + for( auto& id : vins_id ) { + auto vin_itr = vins_info_idx.find( id ); + auto addr_itr = btc_addr_idx.find( vin_itr->address ); + + result.push_back( addr_itr->owner ); + } + + return result; +} + + + +} } // graphene::chain \ No newline at end of file diff --git a/tests/tests/bitcoin_issue_tests.cpp b/tests/tests/bitcoin_issue_tests.cpp new file mode 100644 index 00000000..723068e4 --- /dev/null +++ b/tests/tests/bitcoin_issue_tests.cpp @@ -0,0 +1,176 @@ +#include +#include "../common/database_fixture.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_SUITE( bitcoin_issue_tests, database_fixture ) + +void create_bitcoin_issue_operation_environment( database& db ) +{ + std::vector< info_for_used_vin_id_type > vins; + std::vector< info_for_vout_id_type > vouts; + + for( auto i = 0; i < 3; i++ ){ + db.create([&]( bitcoin_address_object& obj ) { + obj.owner = account_id_type( i ); + obj.address.address = std::to_string( i ); + }); + + auto vin_id = db.create([&]( info_for_used_vin_object& obj ) { + obj.identifier = fc::sha256( std::to_string( i ) ); + obj.out.amount = 100000 + i * 100000; + obj.address = std::to_string( i ); + }).get_id(); + + auto vout_id = db.create([&]( info_for_vout_object& obj ) { + obj.payer = account_id_type( i ); + obj.amount = 100000 + i * 100000; + obj.address = std::to_string( i ); + }).get_id(); + + vins.push_back( vin_id ); + vouts.push_back( vout_id ); + } + + db.create([&]( bitcoin_transaction_object& obj ) { + obj.pw_vin = db.pw_vout_manager.get_latest_unused_vout()->hash_id; + obj.vins = vins; + obj.vouts = vouts; + obj.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + }); +} + +BOOST_AUTO_TEST_CASE( check_deleting_all_btc_transaction_information ) +{ + transaction_evaluation_state context(&db); + + const auto& btc_trx_idx = db.get_index_type().indices().get(); + const auto& vins_info_idx = db.get_index_type().indices().get(); + const auto& vouts_info_idx = db.get_index_type().indices().get(); + + create_bitcoin_issue_operation_environment( db ); + db.bitcoin_confirmations.insert( sidechain::bitcoin_transaction_confirmations( fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ) ) ); + + FC_ASSERT( btc_trx_idx.size() == 1 ); + FC_ASSERT( vins_info_idx.size() == 3 ); + FC_ASSERT( vouts_info_idx.size() == 3 ); + FC_ASSERT( db.bitcoin_confirmations.size() == 1 ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + db.apply_operation( context, op ); + + FC_ASSERT( btc_trx_idx.size() == 0 ); + FC_ASSERT( vins_info_idx.size() == 0 ); + FC_ASSERT( vouts_info_idx.size() == 0 ); + FC_ASSERT( db.bitcoin_confirmations.size() == 0 ); +} + +BOOST_AUTO_TEST_CASE( check_adding_issue_to_accounts ) +{ + transaction_evaluation_state context(&db); + const auto& btc_trx_idx = db.get_index_type().indices().get(); + + create_bitcoin_issue_operation_environment( db ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + + db.apply_operation( context, op ); + + for( auto i = 0; i < 3; i++ ){ + auto itr = btc_trx_idx.find( boost::make_tuple( account_id_type( i ), db.get_sidechain_asset_id() ) ); + FC_ASSERT( itr != btc_trx_idx.end() ); + + FC_ASSERT( itr->balance == 100000 + i * 100000 ); + } +} + +BOOST_AUTO_TEST_CASE( check_bitcoin_issue_operation_throw ) +{ + transaction_evaluation_state context(&db); + + const auto& btc_trx_idx = db.get_index_type().indices().get(); + const auto& btc_addr_idx = db.get_index_type().indices().get(); + const auto& vins_info_idx = db.get_index_type().indices().get(); + const auto& vouts_info_idx = db.get_index_type().indices().get(); + + create_bitcoin_issue_operation_environment( db ); + db.bitcoin_confirmations.insert( sidechain::bitcoin_transaction_confirmations( fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ) ) ); + + { + auto session = db._undo_db.start_undo_session(); + + db.remove( *vouts_info_idx.begin() ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } + + { + auto session = db._undo_db.start_undo_session(); + + db.remove( *vins_info_idx.begin() ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } + + { + auto session = db._undo_db.start_undo_session(); + + db.remove( *btc_addr_idx.begin() ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } + + { + auto session = db._undo_db.start_undo_session(); + + db.remove( *btc_trx_idx.begin() ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } + + { + auto session = db._undo_db.start_undo_session(); + + bitcoin_issue_operation op; + op.payer = account_id_type(1); + op.transaction_id = fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ); + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } +} + + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file