diff --git a/.gitignore b/.gitignore index 5df52280d..bdcefcdc3 100644 --- a/.gitignore +++ b/.gitignore @@ -25,6 +25,11 @@ tests/chain_bench tests/chain_test tests/intense_test tests/performance_test +tests/betting_test +tests/tournament_test +tests/random_test +tests/sidechain_test +tests/generate_empty_blocks/generate_empty_blocks doxygen diff --git a/CMakeLists.txt b/CMakeLists.txt index a71bc0633..581b55824 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -60,7 +60,7 @@ IF( WIN32 ) set(BOOST_ALL_DYN_LINK OFF) # force dynamic linking for all libraries ENDIF(WIN32) -FIND_PACKAGE(Boost 1.57 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) +FIND_PACKAGE(Boost 1.60 REQUIRED COMPONENTS ${BOOST_COMPONENTS}) # For Boost 1.53 on windows, coroutine was not in BOOST_LIBRARYDIR and do not need it to build, but if boost versin >= 1.54, find coroutine otherwise will cause link errors IF(NOT "${Boost_VERSION}" MATCHES "1.53(.*)") SET(BOOST_LIBRARIES_TEMP ${Boost_LIBRARIES}) @@ -132,6 +132,9 @@ else( WIN32 ) # Apple AND Linux if( "${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU" ) set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fno-builtin-memcmp" ) + if ( CMAKE_CXX_COMPILER_VERSION VERSION_GREATER 6.0 ) + set( CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fpermissive" ) + endif() 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/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index be71012dc..6196b4c91 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -10,3 +10,4 @@ add_subdirectory( utilities ) add_subdirectory( app ) add_subdirectory( plugins ) add_subdirectory( wallet ) +add_subdirectory( sidechain ) diff --git a/libraries/app/application.cpp b/libraries/app/application.cpp index 5e4f9c7e4..1029d6fdc 100644 --- a/libraries/app/application.cpp +++ b/libraries/app/application.cpp @@ -538,7 +538,9 @@ namespace detail { // you can help the network code out by throwing a block_older_than_undo_history exception. // when the net code sees that, it will stop trying to push blocks from that chain, but // leave that peer connected so that they can get sync blocks from us - bool result = _chain_db->push_block(blk_msg.block, (_is_block_producer | _force_validate) ? database::skip_nothing : database::skip_transaction_signatures); + auto skip_nothing_mode = sync_mode ? database::skip_nothing | database::skip_btc_tx_sending : database::skip_nothing; + auto skip_transaction_signatures_mode = sync_mode ? database::skip_transaction_signatures | database::skip_btc_tx_sending : database::skip_transaction_signatures; + bool result = _chain_db->push_block(blk_msg.block, (_is_block_producer | _force_validate) ? skip_nothing_mode : skip_transaction_signatures_mode); // the block was accepted, so we now know all of the transactions contained in the block if (!sync_mode) @@ -963,6 +965,11 @@ void application::set_program_options(boost::program_options::options_descriptio ("genesis-json", bpo::value(), "File to read Genesis State from") ("dbg-init-key", bpo::value(), "Block signing key to use for init witnesses, overrides genesis file") ("api-access", bpo::value(), "JSON file specifying API permissions") + ("bitcoin-node-ip", bpo::value()->implicit_value("127.0.0.1"), "IP address of Bitcoin node") + ("bitcoin-node-zmq-port", bpo::value()->implicit_value(28332), "ZMQ port of Bitcoin node") + ("bitcoin-node-rpc-port", bpo::value()->implicit_value(18332), "RPC port of Bitcoin node") + ("bitcoin-node-rpc-user", bpo::value(), "Bitcoin RPC user") + ("bitcoin-node-rpc-password", bpo::value(), "Bitcoin RPC password") ; command_line_options.add(configuration_file_options); command_line_options.add_options() diff --git a/libraries/app/database_api.cpp b/libraries/app/database_api.cpp index d3af2f297..dea61ea58 100644 --- a/libraries/app/database_api.cpp +++ b/libraries/app/database_api.cpp @@ -161,6 +161,9 @@ class database_api_impl : public std::enable_shared_from_this vector get_tournaments_by_state(tournament_id_type stop, unsigned limit, tournament_id_type start, tournament_state state); vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; + //Sidechain + vector get_bitcoin_addresses(const account_id_type& acc_id) const; + //private: template @@ -172,7 +175,6 @@ class database_api_impl : public std::enable_shared_from_this if( !is_subscribed_to_item(i) ) { - idump((i)); _subscribe_filter.insert( vec.data(), vec.size() );//(vecconst char*)&i, sizeof(i) ); } } @@ -2021,6 +2023,32 @@ vector database_api_impl::get_registered_tournaments(account return tournament_ids; } +////////////////////////////////////////////////////////////////////// +// // +// Sidechain // +// // +////////////////////////////////////////////////////////////////////// + +vector database_api::get_bitcoin_addresses(const account_id_type& acc) const +{ + return my->get_bitcoin_addresses(acc); +} + +vector database_api_impl::get_bitcoin_addresses(const account_id_type& acc) const +{ + vector result; + + const auto& btc_addr_idx = _db.get_index_type().indices().get(); + + auto itr = btc_addr_idx.lower_bound( acc ); + while( itr != btc_addr_idx.end() && itr->owner == acc ) + { + result.push_back( *itr ); + ++itr; + } + return result; +} + ////////////////////////////////////////////////////////////////////// // // // Private methods // diff --git a/libraries/app/impacted.cpp b/libraries/app/impacted.cpp index 9d64cf11b..9c72ca0cf 100644 --- a/libraries/app/impacted.cpp +++ b/libraries/app/impacted.cpp @@ -282,6 +282,30 @@ struct get_impacted_account_visitor _impacted.insert( op.affiliate ); } void operator()( const affiliate_referral_payout_operation& op ) { } + void operator()( const withdraw_pbtc_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_address_create_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_send_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_sign_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_issue_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_revert_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 7b0943e42..5342f8484 100644 --- a/libraries/app/include/graphene/app/database_api.hpp +++ b/libraries/app/include/graphene/app/database_api.hpp @@ -47,6 +47,7 @@ #include #include #include +#include #include @@ -644,6 +645,11 @@ class database_api * @return the list of tournaments that a given account is registered to play in */ vector get_registered_tournaments(account_id_type account_filter, uint32_t limit) const; + + /** + * @return the list of bitcoin addresses which belong to acc_id + */ + vector get_bitcoin_addresses(const account_id_type& acc_id) const; private: std::shared_ptr< database_api_impl > my; @@ -765,4 +771,5 @@ FC_API(graphene::app::database_api, (get_tournaments_by_state) (get_tournaments ) (get_registered_tournaments) + (get_bitcoin_addresses) ) diff --git a/libraries/chain/CMakeLists.txt b/libraries/chain/CMakeLists.txt index a328cf1f1..59c93fd2f 100644 --- a/libraries/chain/CMakeLists.txt +++ b/libraries/chain/CMakeLists.txt @@ -21,6 +21,7 @@ if( GRAPHENE_DISABLE_UNITY_BUILD ) db_market.cpp db_update.cpp db_witness_schedule.cpp + db_sidechain.cpp ) message( STATUS "Graphene database unity build disabled" ) else( GRAPHENE_DISABLE_UNITY_BUILD ) @@ -85,6 +86,9 @@ add_library( graphene_chain confidential_evaluator.cpp special_authority.cpp buyback.cpp + bitcoin_address_evaluator.cpp + bitcoin_transaction_evaluator.cpp + sidechain_evaluator.cpp account_object.cpp asset_object.cpp @@ -110,6 +114,7 @@ add_library( graphene_chain betting_market_group_object.cpp affiliate_payout.cpp + withdraw_pbtc_evaluator.cpp ${HEADERS} ${PROTOCOL_HEADERS} @@ -117,7 +122,8 @@ add_library( graphene_chain ) add_dependencies( graphene_chain build_hardfork_hpp ) -target_link_libraries( graphene_chain fc graphene_db ) + +target_link_libraries( graphene_chain fc graphene_db sidechain ) target_include_directories( graphene_chain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" "${CMAKE_CURRENT_BINARY_DIR}/include" ) diff --git a/libraries/chain/bitcoin_address_evaluator.cpp b/libraries/chain/bitcoin_address_evaluator.cpp new file mode 100644 index 000000000..ab9cb6e13 --- /dev/null +++ b/libraries/chain/bitcoin_address_evaluator.cpp @@ -0,0 +1,46 @@ +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result bitcoin_address_create_evaluator::do_evaluate( const bitcoin_address_create_operation& op ) +{ + database& d = db(); + FC_ASSERT( !d.is_sidechain_fork_needed() ); + auto& acc_idx = d.get_index_type().indices().get(); + auto acc_itr = acc_idx.find( op.owner ); + FC_ASSERT( acc_itr != acc_idx.end() ); + return void_result(); +} + +object_id_type bitcoin_address_create_evaluator::do_apply( const bitcoin_address_create_operation& op ) +{ + database& d = db(); + const auto pw_obj = d.get_latest_PW(); + auto witnesses_keys = pw_obj.address.witnesses_keys; + + const auto& new_btc_address = d.create( [&]( bitcoin_address_object& a ) { + witnesses_keys.erase( --witnesses_keys.end() ); + witnesses_keys.emplace( d.get_sidechain_account_id(), pubkey_from_id( a.id ) ); + + a.owner = op.owner; + a.address = sidechain::btc_multisig_segwit_address( SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG, witnesses_keys ); + a.count_invalid_pub_key = 1; + }); + + return new_btc_address.id; +} + +public_key_type bitcoin_address_create_evaluator::pubkey_from_id( object_id_type id ) +{ + fc::ecc::public_key_data pubkey_data; + + pubkey_data.at( 0 ) = 0x02; // version + const auto hash = fc::sha256::hash( std::to_string( id.instance() ) ) ; + std::copy( hash.data(), hash.data() + hash.data_size(), pubkey_data.begin() + 1 ); + + return public_key_type( pubkey_data ); +} +} } // namespace graphene::chain diff --git a/libraries/chain/bitcoin_transaction_evaluator.cpp b/libraries/chain/bitcoin_transaction_evaluator.cpp new file mode 100644 index 000000000..95ea95105 --- /dev/null +++ b/libraries/chain/bitcoin_transaction_evaluator.cpp @@ -0,0 +1,274 @@ +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace graphene { namespace chain { + +using namespace sidechain; + +void_result bitcoin_transaction_send_evaluator::do_evaluate( const bitcoin_transaction_send_operation& op ) +{ + // FC_ASSERT( db().get_sidechain_account_id() == op.payer ); + return void_result(); +} + +object_id_type bitcoin_transaction_send_evaluator::do_apply( const bitcoin_transaction_send_operation& op ) +{ + bitcoin_transaction_send_operation& mutable_op = const_cast( op ); + database& d = db(); + + finalize_bitcoin_transaction( mutable_op ); + + auto new_vins = create_transaction_vins( mutable_op ); + + const bitcoin_transaction_object& btc_tx = d.create< bitcoin_transaction_object >( [&]( bitcoin_transaction_object& obj ) + { + obj.pw_vin = mutable_op.pw_vin.identifier; + obj.vins = new_vins; + obj.vouts = mutable_op.vouts; + obj.transaction = mutable_op.transaction; + obj.transaction_id = mutable_op.transaction.get_txid(); + obj.fee_for_size = mutable_op.fee_for_size; + }); + + sidechain::prev_out new_pw_vout = { "", 0, 0 }; + sidechain::bytes wit_script = d.get_latest_PW().address.get_witness_script(); + if( btc_tx.transaction.vout[0].is_p2wsh() && btc_tx.transaction.vout[0].scriptPubKey == wit_script ) + new_pw_vout = { btc_tx.transaction.get_txid(), 0, btc_tx.transaction.vout[0].value }; + + d.pw_vout_manager.create_new_vout( new_pw_vout ); + send_bitcoin_transaction( btc_tx ); + + return btc_tx.id; +} + +std::vector bitcoin_transaction_send_evaluator::create_transaction_vins( bitcoin_transaction_send_operation& op ) +{ + database& d = db(); + std::vector new_vins; + + for( auto itr : op.vins ) { + auto info_for_used_vin_itr = d.create< info_for_used_vin_object >( [&]( info_for_used_vin_object& obj ) + { + obj.identifier = itr.identifier; + obj.out = itr.out; + obj.address = itr.address; + obj.script = itr.script; + }); + new_vins.push_back( info_for_used_vin_itr.identifier ); + + auto obj_itr = d.i_w_info.find_info_for_vin( itr.identifier ); + if( obj_itr.valid() ) { + d.i_w_info.remove_info_for_vin( *obj_itr ); + } + } + + return new_vins; +} + +void bitcoin_transaction_send_evaluator::finalize_bitcoin_transaction( bitcoin_transaction_send_operation& op ) +{ + database& d = db(); + + auto vins = op.vins; + if( op.pw_vin.identifier.str().compare( 0, 48, SIDECHAIN_NULL_VIN_IDENTIFIER ) != 0 ) { + vins.insert( vins.begin(), op.pw_vin ); + } + + std::vector redeem_scripts( d.i_w_info.get_redeem_scripts( vins ) ); + std::vector amounts( d.i_w_info.get_amounts( vins ) ); + + std::vector> new_stacks( sidechain::sort_sigs( op.transaction, redeem_scripts, amounts, d.context_verify ) ); + + for( size_t i = 0; i < new_stacks.size(); i++ ) { + op.transaction.vin[i].scriptWitness = new_stacks[i]; + } + + sidechain::sign_witness_transaction_finalize( op.transaction, redeem_scripts ); +} + +void bitcoin_transaction_send_evaluator::send_bitcoin_transaction( const bitcoin_transaction_object& btc_tx ) +{ + database& d = db(); + uint32_t skip = d.get_node_properties().skip_flags; + if( !(skip & graphene::chain::database::skip_btc_tx_sending) && d.send_btc_tx_flag ){ + idump((btc_tx)); + d.send_btc_tx( btc_tx.transaction ); + } +} + +void_result bitcoin_transaction_sign_evaluator::do_evaluate( const bitcoin_transaction_sign_operation& op ) +{ + database& d = db(); + + const auto& proposal_idx = d.get_index_type().indices().get(); + const auto& proposal_itr = proposal_idx.find( op.proposal_id ); + FC_ASSERT( proposal_idx.end() != proposal_itr, "proposal not found"); + + const auto& witness_obj = d.get< witness_object >( d._current_witness_id ); + FC_ASSERT( witness_obj.witness_account == op.payer, "Incorrect witness." ); + + sidechain::bytes public_key( public_key_data_to_bytes( witness_obj.signing_key.key_data ) ); + auto btc_send_op = proposal_itr->proposed_transaction.operations[0].get(); + + auto vins = btc_send_op.vins; + if( btc_send_op.pw_vin.identifier.str().compare( 0, 48, SIDECHAIN_NULL_VIN_IDENTIFIER ) != 0 ) { + vins.insert( vins.begin(), btc_send_op.pw_vin ); + } + + FC_ASSERT( check_sigs( public_key, op.signatures, vins, btc_send_op.transaction ) ); + + sidechain::sidechain_proposal_checker checker( d ); + FC_ASSERT( checker.check_witness_opportunity_to_approve( witness_obj, *proposal_itr ), "Can't sign this transaction" ); + + return void_result(); +} + +void_result bitcoin_transaction_sign_evaluator::do_apply( const bitcoin_transaction_sign_operation& op ) +{ + database& d = db(); + const auto& proposal = op.proposal_id( d ); + + d.modify( proposal, [&]( proposal_object& po ) { + auto bitcoin_transaction_send_op = po.proposed_transaction.operations[0].get(); + for( size_t i = 0; i < op.signatures.size(); i++ ) { + bitcoin_transaction_send_op.transaction.vin[i].scriptWitness.push_back( op.signatures[i] ); + } + po.proposed_transaction.operations[0] = bitcoin_transaction_send_op; + }); + + update_proposal( op ); + + return void_result(); +} + +void bitcoin_transaction_sign_evaluator::update_proposal( const bitcoin_transaction_sign_operation& op ) +{ + database& d = db(); + proposal_update_operation update_op; + + update_op.fee_paying_account = op.payer; + update_op.proposal = op.proposal_id; + update_op.active_approvals_to_add = { op.payer }; + + 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; + + d.apply_operation( *trx_state, update_op ); + + trx_state->skip_fee = skip_fee_old; + trx_state->skip_fee_schedule_check = skip_fee_schedule_check_old; +} + +bool bitcoin_transaction_sign_evaluator::check_sigs( const bytes& key_data, const std::vector& sigs, + const std::vector& info_for_vins, + const bitcoin_transaction& tx ) +{ + FC_ASSERT( sigs.size() == info_for_vins.size() && sigs.size() == tx.vin.size() ); + const auto& bitcoin_address_idx = db().get_index_type().indices().get< by_address >(); + + for( size_t i = 0; i < tx.vin.size(); i++ ) { + const auto pbtc_address = bitcoin_address_idx.find( info_for_vins[i].address ); + const bytes& script = pbtc_address->address.get_redeem_script(); + + const auto& sighash_str = get_signature_hash( tx, script, static_cast( info_for_vins[i].out.amount ), i, 1, true ).str(); + const bytes& sighash_hex = parse_hex( sighash_str ); + + if( !verify_sig( sigs[i], key_data, sighash_hex, db().context_verify ) ) { + return false; + } + + size_t count_sigs = 0; + for( auto& s : tx.vin[i].scriptWitness ) { + if( verify_sig( s, key_data, sighash_hex, db().context_verify ) ) { + count_sigs++; + } + } + + std::vector pubkeys = get_pubkey_from_redeemScript( script ); + size_t count_pubkeys = std::count( pubkeys.begin(), pubkeys.end(), key_data ); + if( count_sigs >= count_pubkeys ) { + return false; + } + + uint32_t position = std::find( op_num.begin(), op_num.end(), script[0] ) - op_num.begin(); + if( !( position >= 0 && position < op_num.size() ) || tx.vin[i].scriptWitness.size() == position + 1 ) { + return false; + } + } + return true; +} + +void_result bitcoin_transaction_revert_evaluator::do_evaluate( const bitcoin_transaction_revert_operation& op ) +{ try { + + database& d = db(); + const auto& btc_trx_idx = d.get_index_type().indices().get(); + const auto& vouts_info_idx = d.get_index_type().indices().get(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + + for( auto trx_info: op.transactions_info ) { + const auto& btc_itr = btc_trx_idx.find( trx_info.transaction_id ); + FC_ASSERT( btc_itr != btc_trx_idx.end() ); + + for( const auto& vout_id : btc_itr->vouts ) { + FC_ASSERT( vouts_info_idx.find( vout_id ) != vouts_info_idx.end() ); + } + + for( const auto& vout_id : btc_itr->vins ) { + FC_ASSERT( vins_info_idx.find( vout_id ) != vins_info_idx.end() ); + } + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result bitcoin_transaction_revert_evaluator::do_apply( const bitcoin_transaction_revert_operation& op ) +{ try { + + database& d = db(); + const auto& btc_trx_idx = d.get_index_type().indices().get(); + const auto& vouts_info_idx = d.get_index_type().indices().get(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + + for( auto trx_info: op.transactions_info ) { + const auto& btc_trx_obj = *btc_trx_idx.find( trx_info.transaction_id ); + d.pw_vout_manager.delete_vouts_after( btc_trx_obj.pw_vin ); + d.pw_vout_manager.mark_as_unused_vout( btc_trx_obj.pw_vin ); + + for( const auto& vout_id : btc_trx_obj.vouts ) { + const auto& vout_obj = *vouts_info_idx.find( vout_id ); + d.modify( vout_obj, [&]( info_for_vout_object& obj ){ + obj.used = false; + }); + } + + for( const auto& vin_id : btc_trx_obj.vins ) { + const auto& vin_obj = *vins_info_idx.find( vin_id ); + + if( trx_info.valid_vins.count( fc::sha256 ( vin_obj.out.hash_tx ) ) ) { + d.i_w_info.insert_info_for_vin( vin_obj.out, vin_obj.address, vin_obj.script, true ); + } + d.remove( vin_obj ); + } + d.bitcoin_confirmations.remove( trx_info.transaction_id ); + d.remove( btc_trx_obj ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +} } diff --git a/libraries/chain/database.cpp b/libraries/chain/database.cpp index 7711f5439..e2bdfa62c 100644 --- a/libraries/chain/database.cpp +++ b/libraries/chain/database.cpp @@ -33,4 +33,5 @@ #include "db_market.cpp" #include "db_update.cpp" #include "db_witness_schedule.cpp" -#include "db_notify.cpp" \ No newline at end of file +#include "db_notify.cpp" +#include "db_sidechain.cpp" diff --git a/libraries/chain/db_balance.cpp b/libraries/chain/db_balance.cpp index a70f077bb..fb723a2b8 100644 --- a/libraries/chain/db_balance.cpp +++ b/libraries/chain/db_balance.cpp @@ -71,9 +71,13 @@ void database::adjust_balance(account_id_type account, asset delta ) } else { if( delta.amount < 0 ) FC_ASSERT( itr->get_balance() >= -delta, "Insufficient Balance: ${a}'s balance of ${b} is less than required ${r}", ("a",account(*this).name)("b",to_pretty_string(itr->get_balance()))("r",to_pretty_string(-delta))); - modify(*itr, [delta](account_balance_object& b) { - b.adjust_balance(delta); - }); + if( delta.amount < 0 && itr->get_balance() == -delta ) { + remove(*itr); + } else { + modify(*itr, [delta](account_balance_object& b) { + b.adjust_balance(delta); + }); + } } } FC_CAPTURE_AND_RETHROW( (account)(delta) ) } diff --git a/libraries/chain/db_block.cpp b/libraries/chain/db_block.cpp index 2650d47c5..60cae1d97 100644 --- a/libraries/chain/db_block.cpp +++ b/libraries/chain/db_block.cpp @@ -182,6 +182,9 @@ void database::check_tansaction_for_duplicated_operations(const signed_transacti bool database::push_block(const signed_block& new_block, uint32_t skip) { // idump((new_block.block_num())(new_block.id())(new_block.timestamp)(new_block.previous)); + + send_btc_tx_flag = true; + bool result; detail::with_skip_flags( *this, skip, [&]() { @@ -339,6 +342,7 @@ processed_transaction database::push_proposal(const proposal_object& proposal) 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)); + remove_sidechain_proposal_object(proposal); remove(proposal); session.merge(); } catch ( const fc::exception& e ) { @@ -399,6 +403,10 @@ signed_block database::_generate_block( auto maximum_block_size = get_global_properties().parameters.maximum_block_size; size_t total_block_size = max_block_header_size; + if( !is_sidechain_fork_needed() ) { + processing_sidechain_proposals( witness_obj, block_signing_private_key ); + } + signed_block pending_block; // @@ -415,6 +423,27 @@ signed_block database::_generate_block( _pending_tx_session.reset(); _pending_tx_session = _undo_db.start_undo_session(); + if( !is_sidechain_fork_needed() ) { + auto op = create_send_btc_tx_proposal( witness_obj ); + if( op.valid() ) { + _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( block_signing_private_key, *op ) ); + } + + auto iss_op = create_bitcoin_issue_proposals( witness_obj ); + if( iss_op.valid() ) { + _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( block_signing_private_key, *iss_op ) ); + } + + auto revert_op = create_bitcoin_revert_proposals( witness_obj ); + if( revert_op.valid() ) { + _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( block_signing_private_key, *revert_op ) ); + } + } + + send_btc_tx_flag = false; + + _current_witness_id = witness_obj.id; + uint64_t postponed_tx_count = 0; // pop pending state (reset to head block state) for( const processed_transaction& tx : _pending_tx ) @@ -566,6 +595,8 @@ void database::apply_block( const signed_block& next_block, uint32_t skip ) skip = ~0;// WE CAN SKIP ALMOST EVERYTHING } + _current_witness_id = next_block.witness; + detail::with_skip_flags( *this, skip, [&]() { _apply_block( next_block ); @@ -579,6 +610,11 @@ void database::_apply_block( const signed_block& next_block ) uint32_t skip = get_node_properties().skip_flags; _applied_ops.clear(); + if( head_block_time() > HARDFORK_SIDECHAIN_TIME && is_sidechain_fork_needed() ) + { + perform_sidechain_fork(); + } + FC_ASSERT( (skip & skip_merkle_check) || next_block.transaction_merkle_root == next_block.calculate_merkle_root(), "", ("next_block.transaction_merkle_root",next_block.transaction_merkle_root)("calc",next_block.calculate_merkle_root())("next_block",next_block)("id",next_block.id()) ); const witness_object& signing_witness = validate_block_header(skip, next_block); diff --git a/libraries/chain/db_getter.cpp b/libraries/chain/db_getter.cpp index 9516e256e..b68b41e0b 100644 --- a/libraries/chain/db_getter.cpp +++ b/libraries/chain/db_getter.cpp @@ -94,8 +94,7 @@ node_property_object& database::node_properties() uint32_t database::last_non_undoable_block_num() const { - return head_block_num() - _undo_db.size(); + return head_block_num() < _undo_db.size() ? 0 : head_block_num() - _undo_db.size(); } - } } diff --git a/libraries/chain/db_init.cpp b/libraries/chain/db_init.cpp index d58c68f75..b64421902 100644 --- a/libraries/chain/db_init.cpp +++ b/libraries/chain/db_init.cpp @@ -49,6 +49,12 @@ #include #include #include +#include +#include +#include +#include +#include +#include #include @@ -76,6 +82,10 @@ #include #include #include +#include +#include +#include +#include #include @@ -85,6 +95,7 @@ #include + namespace graphene { namespace chain { // C++ requires that static class variables declared and initialized @@ -237,6 +248,12 @@ void database::initialize_evaluators() register_evaluator(); register_evaluator(); register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); + register_evaluator(); } void database::initialize_indexes() @@ -301,6 +318,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 >(); + add_index< primary_index >(); + add_index< primary_index >(); } void database::init_genesis(const genesis_state_type& genesis_state) diff --git a/libraries/chain/db_management.cpp b/libraries/chain/db_management.cpp index 6bcee4bde..f528b7c90 100644 --- a/libraries/chain/db_management.cpp +++ b/libraries/chain/db_management.cpp @@ -36,15 +36,22 @@ namespace graphene { namespace chain { database::database() : - _random_number_generator(fc::ripemd160().data()) + i_w_info(*this), pw_vout_manager(*this), _random_number_generator(fc::ripemd160().data()) { initialize_indexes(); initialize_evaluators(); + context_sign = secp256k1_context_create(SECP256K1_CONTEXT_SIGN); + context_verify = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY); + estimated_feerate = 0; } database::~database() { clear_pending(); + secp256k1_context_destroy(context_sign); + context_sign = nullptr; + secp256k1_context_destroy(context_verify); + context_verify = nullptr; } void database::reindex(fc::path data_dir, const genesis_state_type& initial_allocation) @@ -101,18 +108,21 @@ void database::reindex(fc::path data_dir, const genesis_state_type& initial_allo skip_transaction_dupe_check | skip_tapos_check | skip_witness_schedule_check | - skip_authority_check); + skip_authority_check | + skip_btc_tx_sending); else apply_block(*block, skip_witness_signature | skip_transaction_signatures | skip_transaction_dupe_check | skip_tapos_check | skip_witness_schedule_check | - skip_authority_check); + skip_authority_check | + skip_btc_tx_sending); } if (!_slow_replays) _undo_db.enable(); auto end = fc::time_point::now(); + restore_bitcoin_transaction_status(); ilog( "Done reindexing, elapsed time: ${t} sec", ("t",double((end-start).count())/1000000.0 ) ); } FC_CAPTURE_AND_RETHROW( (data_dir) ) } diff --git a/libraries/chain/db_notify.cpp b/libraries/chain/db_notify.cpp index 53ec524df..f0c02a12e 100644 --- a/libraries/chain/db_notify.cpp +++ b/libraries/chain/db_notify.cpp @@ -269,6 +269,30 @@ struct get_impacted_account_visitor _impacted.insert( op.affiliate ); } void operator()( const affiliate_referral_payout_operation& op ) { } + void operator()( const withdraw_pbtc_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_address_create_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_send_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_sign_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_issue_operation& op ) + { + _impacted.insert( op.payer ); + } + void operator()( const bitcoin_transaction_revert_operation& op ) + { + _impacted.insert( op.payer ); + } }; void operation_get_impacted_accounts( const operation& op, flat_set& result ) diff --git a/libraries/chain/db_sidechain.cpp b/libraries/chain/db_sidechain.cpp new file mode 100644 index 000000000..ad386e815 --- /dev/null +++ b/libraries/chain/db_sidechain.cpp @@ -0,0 +1,432 @@ +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +using namespace sidechain; + +namespace graphene { namespace chain { + +std::map< account_id_type, public_key_type> database::get_active_witnesses_keys() const +{ + const auto& witnesses_by_id = get_index_type().indices().get(); + std::map< account_id_type, public_key_type > witnesses_keys; + auto& active_witnesses = get_global_properties().active_witnesses; + for( auto witness_id : active_witnesses ) { + const auto& witness_obj = witnesses_by_id.find( witness_id ); + if( witness_obj != witnesses_by_id.end() ){ + witnesses_keys.emplace( witness_obj->witness_account, witness_obj->signing_key ); + } + } + return witnesses_keys; +} + +bool database::is_sidechain_fork_needed() const +{ + const auto& params = get_global_properties().parameters.extensions.value.sidechain_parameters; + return !params; +} + +void database::perform_sidechain_fork() +{ + const auto& sidechain_account = create( [&]( account_object& obj ) { + obj.name = "sidechain_account"; + obj.statistics = create([&]( account_statistics_object& acc_stat ){ acc_stat.owner = obj.id; }).id; + obj.owner.weight_threshold = 5; + obj.active.weight_threshold = 5; + obj.membership_expiration_date = time_point_sec::maximum(); + obj.network_fee_percentage = GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + obj.lifetime_referrer_fee_percentage = GRAPHENE_100_PERCENT - GRAPHENE_DEFAULT_NETWORK_PERCENT_OF_FEE; + }); + + const asset_object& new_asset = create( [&]( asset_object& obj ) { + obj.symbol = SIDECHAIN_SYMBOL; + obj.precision = SIDECHAIN_PRECISION_DIGITS; + obj.issuer = sidechain_account.get_id(); + obj.options.max_supply = SIDECHAIN_MAX_SHARE_SUPPLY; + obj.options.issuer_permissions = 0; + obj.options.flags = 0; + obj.dynamic_asset_data_id = create([&]( asset_dynamic_data_object& a ) { a.current_supply = 0; }).id; + }); + + modify( get_global_properties(), [&]( global_property_object& gpo ) { + sidechain_parameters_extension params_ext; + params_ext.managing_account = sidechain_account.get_id(); + params_ext.asset_id = new_asset.get_id(); + + gpo.parameters.extensions.value.sidechain_parameters = params_ext; + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.sidechain_parameters = params_ext; + }); + + auto global_properties = get_global_properties(); + const auto& witnesses_idx = get_index_type().indices().get(); + std::vector witness_accounts; + + for( auto witness_id : global_properties.active_witnesses ) { + const auto& witness_obj = witnesses_idx.find( witness_id ); + if( witness_obj != witnesses_idx.end() ) + witness_accounts.push_back( witness_obj->witness_account ); + } + + modify( sidechain_account, [&]( account_object& obj ) { + for( auto& a : witness_accounts ) { + obj.owner.add_authority( a, 1 ); + obj.active.add_authority( a, 1 ); + } + }); + + create( [&]( bitcoin_address_object& pw ) { // Create PW address + pw.address = btc_multisig_segwit_address( 5, get_active_witnesses_keys() ); + pw.owner = sidechain_account.get_id(); + pw.count_invalid_pub_key = 1; + }); + + pw_vout_manager.create_new_vout( {"", 0, 0 } ); +} + +const sidechain_parameters_extension& database::get_sidechain_params() const +{ + const auto& params = get_global_properties().parameters.extensions.value.sidechain_parameters; + FC_ASSERT( params.valid() ); + return *params; +} + +const account_id_type& database::get_sidechain_account_id() const +{ + return get_sidechain_params().managing_account; +} + +const asset_id_type& database::get_sidechain_asset_id() const +{ + return get_sidechain_params().asset_id; +} + +bitcoin_address_object database::get_latest_PW() const +{ + const auto& btc_addr_idx = get_index_type().indices().get(); + auto itr = btc_addr_idx.upper_bound( get_sidechain_account_id() ); + return *(--itr); +} + +int64_t database::get_estimated_fee( size_t tx_vsize, uint64_t estimated_feerate ) { + static const uint64_t default_feerate = 1000; + static const uint64_t min_relay_fee = 1000; + + const auto feerate = std::max( default_feerate, estimated_feerate ); + const auto fee = feerate * int64_t( tx_vsize ) / 1000; + + return std::max( min_relay_fee, fee ); +} + +void database::processing_sidechain_proposals( const witness_object& current_witness, const fc::ecc::private_key& private_key ) +{ + const auto& sidechain_proposal_idx = get_index_type().indices().get< by_id >(); + const auto& proposal_idx = get_index_type().indices().get< by_id >(); + + sidechain_proposal_checker checker( *this ); + + auto approve_propose = [ & ]( 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& sidechain_proposal : sidechain_proposal_idx ) { + + const auto& proposal = proposal_idx.find( sidechain_proposal.proposal_id ); + FC_ASSERT( proposal != proposal_idx.end() ); + + if( !checker.check_reuse( proposal->proposed_transaction.operations.back() ) ) { + continue; + } + + switch( sidechain_proposal.proposal_type ) { + case sidechain_proposal_type::ISSUE_BTC :{ + approve_propose( proposal->id ); + break; + } + case sidechain_proposal_type::SEND_BTC_TRANSACTION :{ + bitcoin_transaction_send_operation op = proposal->proposed_transaction.operations.back().get(); + if( checker.check_bitcoin_transaction_send_operation( op ) && + checker.check_witnesses_keys( current_witness, *proposal ) && + checker.check_witness_opportunity_to_approve( current_witness, *proposal ) ) { + const auto& sign_operation = create_sign_btc_tx_operation( current_witness, private_key, proposal->id ); + _pending_tx.insert( _pending_tx.begin(), create_signed_transaction( private_key, sign_operation ) ); + } + break; + } + case sidechain_proposal_type::REVERT_BTC_TRANSACTION :{ + bitcoin_transaction_revert_operation op = proposal->proposed_transaction.operations.back().get(); + if( checker.check_bitcoin_transaction_revert_operation( op ) ) + approve_propose( proposal->id ); + break; + } + } + } +} + +full_btc_transaction database::create_btc_transaction( const std::vector& info_vins, + const std::vector& info_vouts, + const info_for_vin& info_pw_vin ) +{ + sidechain_condensing_tx ctx( info_vins, info_vouts ); + + + if( info_pw_vin.identifier.str().compare( 0, 48, SIDECHAIN_NULL_VIN_IDENTIFIER ) != 0 ) { + ctx.create_pw_vin( info_pw_vin ); + } + + const auto& pw_address = get_latest_PW().address; + if( info_vouts.size() > 0 ) { + ctx.create_vouts_for_witness_fee( pw_address.witnesses_keys ); + } + + const uint64_t& change = ctx.get_amount_vins() - ctx.get_amount_transfer_to_bitcoin(); + if( change > 0 ) { + ctx.create_pw_vout( change, pw_address.get_witness_script() ); + } + + const uint64_t& size_fee = get_estimated_fee( sidechain_condensing_tx::get_estimate_tx_size( ctx.get_transaction(), pw_address.witnesses_keys.size() ), estimated_feerate.load() ); + ctx.subtract_fee( size_fee, get_sidechain_params().percent_payment_to_witnesses ); + + return std::make_pair( ctx.get_transaction(), size_fee ); +} + +bool database::delete_invalid_amount_with_fee( sidechain::input_withdrawal_info& i_w_info, std::vector info_vins, const uint64_t& fee, const uint64_t& count_transfer_vout ) +{ + uint64_t size_fee = fee / ( info_vins.size() + count_transfer_vout ); + bool deleted = false; + for( auto info: info_vins ) { + if( info.out.amount < size_fee ) { + wlog("Amount is too small, vin will be ignored (prevout hash tx = ${hash_tx})",("hash_tx", info.out.hash_tx)); + i_w_info.remove_info_for_vin( info ); + deleted = true; + } + } + return deleted; +}; + +database::full_btc_tx_and_new_vins database::create_tx_with_valid_vin( const std::vector& info_vouts, + const info_for_vin& info_pw_vin ) +{ + // we have to be sure, that we have enough BTC on vin to pay size_fee + full_btc_transaction btc_tx_and_fee; + std::vector info_vins = i_w_info.get_info_for_vins(); + bool deleted = true; + + while( info_vins.size() && deleted ) { + btc_tx_and_fee = create_btc_transaction( info_vins, info_vouts, info_pw_vin ); + deleted = delete_invalid_amount_with_fee( i_w_info, info_vins, btc_tx_and_fee.second, info_vouts.size() ); + info_vins = i_w_info.get_info_for_vins(); + } + + return std::make_pair( btc_tx_and_fee, info_vins ); +} + +fc::optional database::create_send_btc_tx_proposal( const witness_object& current_witness ) +{ + const auto& info_vins = i_w_info.get_info_for_vins(); + const auto& info_vouts = i_w_info.get_info_for_vouts(); + const auto& info_pw_vin = i_w_info.get_info_for_pw_vin(); + + if( info_pw_vin.valid() && ( info_vins.size() || info_vouts.size() ) ) { + // tx_and_new_vins.first = sidechain::full_btc_transaction; tx_and_new_vins.second = std::vector + const auto& tx_and_new_vins = create_tx_with_valid_vin( info_vouts, *info_pw_vin ); + if( !tx_and_new_vins.second.size() && !info_vouts.size() ) + return fc::optional(); + + const auto& btc_tx_and_size_fee = tx_and_new_vins.first; + + bitcoin_transaction_send_operation btc_send_op; + btc_send_op.payer = get_sidechain_account_id(); + btc_send_op.pw_vin = *info_pw_vin; + btc_send_op.vins = tx_and_new_vins.second; + for( auto& out : info_vouts ) { + btc_send_op.vouts.push_back( out.get_id() ); + } + btc_send_op.transaction = btc_tx_and_size_fee.first; + btc_send_op.fee_for_size = btc_tx_and_size_fee.second; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = current_witness.witness_account; + proposal_op.proposed_ops.push_back( op_wrapper( btc_send_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 ); + return proposal_op; + } + return fc::optional(); +} + +signed_transaction database::create_signed_transaction( const private_key& signing_private_key, const operation& op ) +{ + signed_transaction processed_trx; + auto dyn_props = get_dynamic_global_properties(); + processed_trx.set_reference_block( dyn_props.head_block_id ); + processed_trx.set_expiration( head_block_time() + get_global_properties().parameters.maximum_time_until_expiration ); + processed_trx.operations.push_back( op ); + current_fee_schedule().set_fee( processed_trx.operations.back() ); + + processed_trx.sign( signing_private_key, get_chain_id() ); + + return processed_trx; +} + +operation database::create_sign_btc_tx_operation( const witness_object& current_witness, const private_key_type& privkey, + const proposal_id_type& proposal_id ) +{ + const auto& proposal_idx = get_index_type().indices().get(); + const auto& proposal_itr = proposal_idx.find( proposal_id ); + bitcoin_transaction_send_operation op = proposal_itr->proposed_transaction.operations.back().get(); + + bitcoin_transaction_sign_operation sign_operation; + sign_operation.payer = current_witness.witness_account; + sign_operation.proposal_id = proposal_id; + const auto secret = privkey.get_secret(); + bytes key(secret.data(), secret.data() + secret.data_size()); + + auto vins = op.vins; + if( op.pw_vin.identifier.str().compare( 0, 48, SIDECHAIN_NULL_VIN_IDENTIFIER ) != 0 ) { + vins.insert( vins.begin(), op.pw_vin ); + } + + std::vector redeem_scripts( i_w_info.get_redeem_scripts( vins ) ); + std::vector amounts( i_w_info.get_amounts( vins ) ); + + sign_operation.signatures = sign_witness_transaction_part( op.transaction, redeem_scripts, amounts, key, context_sign, 1 ); + + return sign_operation; +} + +void database::remove_sidechain_proposal_object( 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 || + proposal.proposed_transaction.operations.back().which() == operation::tag::value ) ) + { + const auto& sidechain_proposal_idx = get_index_type().indices().get(); + auto sidechain_proposal_itr = sidechain_proposal_idx.find( proposal.id ); + if( sidechain_proposal_itr == sidechain_proposal_idx.end() ) { + return; + } + remove( *sidechain_proposal_itr ); + } +} FC_CAPTURE_AND_RETHROW( (proposal) ) } + +void database::roll_back_vin_and_vout( const proposal_object& proposal ) +{ + if( proposal.proposed_transaction.operations.size() == 1 && + proposal.proposed_transaction.operations.back().which() == operation::tag::value ) + { + bitcoin_transaction_send_operation op = proposal.proposed_transaction.operations.back().get(); + + if( pw_vout_manager.get_vout( op.pw_vin.identifier ).valid() ) { + pw_vout_manager.mark_as_unused_vout( op.pw_vin.identifier ); + } + + for( const auto& vin : op.vins ) { + const auto& v = i_w_info.find_info_for_vin( vin.identifier ); + if( v.valid() ) { + i_w_info.mark_as_unused_vin( vin ); + } + } + + for( const auto& vout : op.vouts ) { + const auto& v = i_w_info.find_info_for_vout( vout ); + if( v.valid() ) { + i_w_info.mark_as_unused_vout( *v ); + } + } + + remove_sidechain_proposal_object( proposal ); + } +} + +fc::optional database::create_bitcoin_issue_proposals( const witness_object& current_witness ) +{ + std::vector trx_ids; + bitcoin_confirmations.safe_for([&]( btc_tx_confirmations_index::index::type::iterator itr_b, btc_tx_confirmations_index::index::type::iterator itr_e ){ + for(auto iter = itr_b; iter != itr_e; iter++) { + if( !iter->is_confirmed_and_not_used() ) return; + + const auto& btc_trx_idx = get_index_type().indices().get(); + const auto& btc_tx = btc_trx_idx.find( iter->transaction_id ); + if( btc_tx == btc_trx_idx.end() ) continue; + trx_ids.push_back( iter->transaction_id ); + } + }); + + if( trx_ids.size() ) { + bitcoin_issue_operation issue_op; + issue_op.payer = get_sidechain_account_id(); + issue_op.transaction_ids = trx_ids; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = current_witness.witness_account; + proposal_op.proposed_ops.push_back( op_wrapper( issue_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 ); + + return fc::optional( proposal_op ); + } + + return fc::optional(); +} + +fc::optional database::create_bitcoin_revert_proposals( const witness_object& current_witness ) +{ + using iter_by_missing = btc_tx_confirmations_index::index::type::iterator; + std::vector trx_info; + + bitcoin_confirmations.safe_for([&]( iter_by_missing itr_b, iter_by_missing itr_e ){ + for(auto iter = itr_b; iter != itr_e; iter++) { + if( !iter->is_missing_and_not_used() ) return; + + const auto& btc_trx_idx = get_index_type().indices().get(); + const auto& btc_tx = btc_trx_idx.find( iter->transaction_id ); + if( btc_tx == btc_trx_idx.end() ) continue; + trx_info.push_back( revert_trx_info( iter->transaction_id, iter->valid_vins ) ); + } + }); + + if( trx_info.size() ) { + bitcoin_transaction_revert_operation revert_op; + revert_op.payer = get_sidechain_account_id(); + revert_op.transactions_info = trx_info; + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = current_witness.witness_account; + proposal_op.proposed_ops.push_back( op_wrapper( revert_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 ); + + return fc::optional( proposal_op ); + } + + return fc::optional(); +} + + +void database::restore_bitcoin_transaction_status() +{ + const auto& btc_tx_idx = get_index_type().indices().get(); + for( const auto& bto : btc_tx_idx ) { + std::set valid_vins; + for( const auto& v : bto.transaction.vin ) { + valid_vins.insert( v.prevout.hash ); + } + bitcoin_confirmations.insert( bitcoin_transaction_confirmations( bto.transaction.get_txid(), valid_vins ) ); + } +} + +} } \ No newline at end of file diff --git a/libraries/chain/db_update.cpp b/libraries/chain/db_update.cpp index ad98837ed..e7a1575e2 100644 --- a/libraries/chain/db_update.cpp +++ b/libraries/chain/db_update.cpp @@ -253,6 +253,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())); } + roll_back_vin_and_vout(proposal); remove(proposal); } } diff --git a/libraries/chain/hardfork.d/sidechain.hf b/libraries/chain/hardfork.d/sidechain.hf new file mode 100644 index 000000000..44400d44b --- /dev/null +++ b/libraries/chain/hardfork.d/sidechain.hf @@ -0,0 +1,4 @@ +// sidechain fardfork +#ifndef HARDFORK_SIDECHAIN_TIME +#define HARDFORK_SIDECHAIN_TIME (fc::time_point_sec( 1550000000 )) +#endif diff --git a/libraries/chain/include/graphene/chain/bitcoin_address_evaluator.hpp b/libraries/chain/include/graphene/chain/bitcoin_address_evaluator.hpp new file mode 100644 index 000000000..8055c6c55 --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_address_evaluator.hpp @@ -0,0 +1,20 @@ +#pragma once + +#include +#include + +namespace graphene { namespace chain { + +class bitcoin_address_create_evaluator : public evaluator +{ +public: + typedef bitcoin_address_create_operation operation_type; + + void_result do_evaluate(const bitcoin_address_create_operation& op); + + object_id_type do_apply(const bitcoin_address_create_operation& op); + + public_key_type pubkey_from_id( object_id_type id ); +}; + +} } // graphene::chain diff --git a/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp b/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp new file mode 100644 index 000000000..5fab6c67f --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_address_object.hpp @@ -0,0 +1,47 @@ +#pragma once + +#include +#include + +#include + +namespace graphene { namespace chain { + +class bitcoin_address_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = bitcoin_address_object_type; + + bitcoin_address_id_type get_id()const { return id; } + // multisig m-of-n (m = 5). Address is valid before count of changed witnesses < 5 + bool valid() { return count_invalid_pub_key < SIDECHAIN_NUMBER_INVALID_KEYS; } + + std::string get_address() const { return address.get_address(); } + + void update_count_invalid_pub_key( const sidechain::accounts_keys& incoming_wit_keys ) { + count_invalid_pub_key = incoming_wit_keys.size() - address.count_intersection( incoming_wit_keys ); + } + + account_id_type owner; + sidechain::btc_multisig_segwit_address address; + uint8_t count_invalid_pub_key; +}; + +struct by_address; +struct by_owner; + +typedef boost::multi_index_container< + bitcoin_address_object, + indexed_by< + ordered_unique< tag, member< object, object_id_type, &object::id > >, + ordered_unique< tag, const_mem_fun< bitcoin_address_object, std::string, &bitcoin_address_object::get_address > >, + ordered_non_unique< tag, member< bitcoin_address_object, account_id_type, &bitcoin_address_object::owner > > + > +> bitcoin_address_multi_index_container; +typedef generic_index bitcoin_address_index; + +} } + +FC_REFLECT_DERIVED( graphene::chain::bitcoin_address_object, (graphene::chain::object), (owner)(address)(count_invalid_pub_key) ) + diff --git a/libraries/chain/include/graphene/chain/bitcoin_transaction_evaluator.hpp b/libraries/chain/include/graphene/chain/bitcoin_transaction_evaluator.hpp new file mode 100644 index 000000000..973a5fcf1 --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_transaction_evaluator.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include + +namespace sidechain { class info_for_vin; } + +namespace graphene { namespace chain { + +class bitcoin_transaction_send_evaluator : public evaluator +{ +public: + typedef bitcoin_transaction_send_operation operation_type; + + void_result do_evaluate( const bitcoin_transaction_send_operation& op ); + + object_id_type do_apply( const bitcoin_transaction_send_operation& op ); + + std::vector create_transaction_vins( bitcoin_transaction_send_operation& op ); + + void finalize_bitcoin_transaction( bitcoin_transaction_send_operation& op ); + + void send_bitcoin_transaction( const bitcoin_transaction_object& btc_tx ); +}; + +class bitcoin_transaction_sign_evaluator : public evaluator +{ + +public: + + typedef bitcoin_transaction_sign_operation operation_type; + + void_result do_evaluate( const bitcoin_transaction_sign_operation& op ); + + void_result do_apply( const bitcoin_transaction_sign_operation& op ); + + void update_proposal( const bitcoin_transaction_sign_operation& op ); + + bool check_sigs( const sidechain::bytes& key_data, const std::vector& sigs, + const std::vector& info_for_vins, const sidechain::bitcoin_transaction& tx ); + +}; + +class bitcoin_transaction_revert_evaluator : public evaluator +{ + +public: + + typedef bitcoin_transaction_revert_operation operation_type; + + void_result do_evaluate( const bitcoin_transaction_revert_operation& op ); + + void_result do_apply( const bitcoin_transaction_revert_operation& op ); +}; + +} } // graphene::chain \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp b/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp new file mode 100644 index 000000000..fa440bc9d --- /dev/null +++ b/libraries/chain/include/graphene/chain/bitcoin_transaction_object.hpp @@ -0,0 +1,41 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +class bitcoin_transaction_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = bitcoin_transaction_object_type; + + bitcoin_transaction_id_type get_id()const { return id; } + + fc::sha256 pw_vin; + + std::vector< fc::sha256 > vins; + std::vector< info_for_vout_id_type > vouts; + + sidechain::bitcoin_transaction transaction; + fc::sha256 transaction_id; + + uint64_t fee_for_size; +}; + +struct by_transaction_id; + +typedef boost::multi_index_container< + bitcoin_transaction_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_unique< tag< by_transaction_id >, member< bitcoin_transaction_object, fc::sha256, &bitcoin_transaction_object::transaction_id > > + > +> bitcoin_transaction_multi_index_container; +typedef generic_index bitcoin_transaction_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::bitcoin_transaction_object, (graphene::chain::object), (pw_vin)(vins)(vouts)(transaction)(transaction_id)(fee_for_size) ) diff --git a/libraries/chain/include/graphene/chain/config.hpp b/libraries/chain/include/graphene/chain/config.hpp index d85cc093e..03532104b 100644 --- a/libraries/chain/include/graphene/chain/config.hpp +++ b/libraries/chain/include/graphene/chain/config.hpp @@ -226,3 +226,18 @@ #define TOURNAMENT_MAX_WHITELIST_LENGTH 1000 #define TOURNAMENT_MAX_START_TIME_IN_FUTURE (60*60*24*7*4) // 1 month #define TOURNAMENT_MAX_START_DELAY (60*60*24*7) // 1 week + + +////////////////////////////////////////////////////////////////////// SideChain +#define SIDECHAIN_SYMBOL "pBTC" +#define SIDECHAIN_PRECISION_DIGITS 8 +#define SIDECHAIN_MAX_SHARE_SUPPLY int64_t(21000000ll * 100000000ll) +#define SIDECHAIN_NUMBER_INVALID_KEYS 5 +#define SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG 5 +#define SIDECHAIN_DEFAULT_NUMBER_OF_CONFIRMATIONS 6 +#define SIDECHAIN_DEFAULT_MAX_UNCONFIRMED_VOUTS 25 +#define SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VINS 5 +#define SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VOUTS 5 +#define SIDECHAIN_DEFAULT_PERCENT_PAYMENT_TO_WITNESSES (GRAPHENE_1_PERCENT/10) +#define SIDECHAIN_NULL_VIN_IDENTIFIER "5feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b4" // fc::sha256::hash( "" + std::to_string( 0 ) ) - ( 8 bytes ) +////////////////////////////////////////////////////////////////////// diff --git a/libraries/chain/include/graphene/chain/database.hpp b/libraries/chain/include/graphene/chain/database.hpp index af50a94b0..504ea5b60 100644 --- a/libraries/chain/include/graphene/chain/database.hpp +++ b/libraries/chain/include/graphene/chain/database.hpp @@ -40,9 +40,20 @@ #include +#include +#include +#include +#include + #include #include +#include + +using namespace fc::ecc; +using sidechain::bitcoin_transaction; +using sidechain::info_for_vin; +using sidechain::info_for_vout; namespace graphene { namespace chain { using graphene::db::abstract_object; @@ -78,7 +89,8 @@ namespace graphene { namespace chain { skip_assert_evaluation = 1 << 8, ///< used while reindexing skip_undo_history_check = 1 << 9, ///< used while reindexing skip_witness_schedule_check = 1 << 10, ///< used while reindexing - skip_validate = 1 << 11 ///< used prior to checkpoint, skips validate() call on transaction + skip_validate = 1 << 11, ///< used prior to checkpoint, skips validate() call on transaction + skip_btc_tx_sending = 1 << 12 }; /** @@ -285,6 +297,7 @@ namespace graphene { namespace chain { uint32_t last_non_undoable_block_num() const; + //////////////////// db_init.cpp //////////////////// void initialize_evaluators(); @@ -503,6 +516,65 @@ namespace graphene { namespace chain { void perform_account_maintenance(std::tuple helpers); ///@} ///@} + //////////////////// db_sidechain.cpp //////////////////// + public: + + using full_btc_tx_and_new_vins = std::pair< sidechain::full_btc_transaction, std::vector >; + + std::map< account_id_type, public_key_type> get_active_witnesses_keys() const; + bool is_sidechain_fork_needed() const; + void perform_sidechain_fork(); + bitcoin_address_object get_latest_PW() const; + + const sidechain::sidechain_parameters_extension& get_sidechain_params() const; + const account_id_type& get_sidechain_account_id() const; + const asset_id_type& get_sidechain_asset_id() const; + int64_t get_estimated_fee( size_t tx_vsize, uint64_t estimated_feerate ); + + void processing_sidechain_proposals( const witness_object& current_witness, const private_key& signing_private_key ); + + inline bool delete_invalid_amount_with_fee( sidechain::input_withdrawal_info& i_w_info, std::vector info_vins, const uint64_t& fee, const uint64_t& count_transfer_vout ); + + full_btc_tx_and_new_vins create_tx_with_valid_vin( const std::vector& info_vouts, + const info_for_vin& info_pw_vin ); + + sidechain::full_btc_transaction create_btc_transaction( const std::vector& info_vins, + const std::vector& info_vouts, + const info_for_vin& info_pw_vin ); + fc::optional create_send_btc_tx_proposal( const witness_object& current_witness ); + operation create_sign_btc_tx_operation( const witness_object& current_witness, const private_key_type& privkey, + const proposal_id_type& proposal_id ); + signed_transaction create_signed_transaction( const private_key& signing_private_key, const operation& op ); + + void remove_sidechain_proposal_object( const proposal_object& proposal ); + + void roll_back_vin_and_vout( const proposal_object& proposal ); + + fc::optional create_bitcoin_issue_proposals( const witness_object& current_witness ); + + fc::optional create_bitcoin_revert_proposals( const witness_object& current_witness ); + + fc::signal send_btc_tx; + + sidechain::input_withdrawal_info i_w_info; + + sidechain::thread_safe_index bitcoin_confirmations; + + sidechain::primary_wallet_vout_manager pw_vout_manager; + + std::atomic estimated_feerate; + + secp256k1_context_t* context_sign; + + secp256k1_context_t* context_verify; + + bool send_btc_tx_flag = true; + + witness_id_type _current_witness_id; + + private: + + void restore_bitcoin_transaction_status(); // db_sidechain vector< processed_transaction > _pending_tx; fork_database _fork_db; diff --git a/libraries/chain/include/graphene/chain/info_for_used_vin_object.hpp b/libraries/chain/include/graphene/chain/info_for_used_vin_object.hpp new file mode 100644 index 000000000..abd4ff67c --- /dev/null +++ b/libraries/chain/include/graphene/chain/info_for_used_vin_object.hpp @@ -0,0 +1,39 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +class info_for_used_vin_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = info_for_used_vin_object_type; + + info_for_used_vin_id_type get_id()const { return id; } + + fc::sha256 identifier; + + sidechain::prev_out out; + std::string address; + sidechain::bytes script; +}; + +struct by_id; +struct by_identifier; + +typedef boost::multi_index_container< + info_for_used_vin_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_unique, member> + > +> info_for_used_vin_multi_index_container; +typedef generic_index info_for_used_vin_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::info_for_used_vin_object, (graphene::chain::object), (identifier)(out)(address)(script) ) diff --git a/libraries/chain/include/graphene/chain/info_for_vout_object.hpp b/libraries/chain/include/graphene/chain/info_for_vout_object.hpp new file mode 100644 index 000000000..ba0ecf881 --- /dev/null +++ b/libraries/chain/include/graphene/chain/info_for_vout_object.hpp @@ -0,0 +1,55 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +class info_for_vout_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = info_for_vout_object_type; + + struct comparer { + bool operator()( const info_for_vout_object& lhs, const info_for_vout_object& rhs ) const { + if( lhs.used != rhs.used ) + return lhs.used < rhs.used; + return lhs.id < rhs.id; + } + }; + + bool operator==( const info_for_vout_object& ifv ) const { + return ( this->payer == ifv.payer ) && + ( this->address == ifv.address ) && + ( this->amount == ifv.amount ); + } + + info_for_vout_id_type get_id()const { return id; } + + account_id_type payer; + sidechain::bitcoin_address address; + uint64_t amount; + + bool used = false; +}; + +struct by_created; +struct by_id_and_not_used; + +typedef boost::multi_index_container< + info_for_vout_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag< by_created >, member< info_for_vout_object, bool, &info_for_vout_object::used > >, + ordered_non_unique, identity< info_for_vout_object >, info_for_vout_object::comparer > + > +> info_for_vout_multi_index_container; +typedef generic_index info_for_vout_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(address)(amount)(used) ) + diff --git a/libraries/chain/include/graphene/chain/primary_wallet_vout_object.hpp b/libraries/chain/include/graphene/chain/primary_wallet_vout_object.hpp new file mode 100644 index 000000000..7d9068b04 --- /dev/null +++ b/libraries/chain/include/graphene/chain/primary_wallet_vout_object.hpp @@ -0,0 +1,44 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +class primary_wallet_vout_object : public abstract_object +{ +public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = primary_wallet_vout_object_type; + + struct comparer { + bool operator()(const primary_wallet_vout_object& lhs, const primary_wallet_vout_object& rhs) const; + }; + + primary_wallet_vout_id_type get_id() const { return id; } + + sidechain::prev_out vout; + fc::sha256 hash_id; // ( sha256(hash + n_vout) - 8 bytes ) + id_obj + + bool confirmed; + bool used; + +}; + +struct by_hash_id; + +typedef boost::multi_index_container< + primary_wallet_vout_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_unique< tag< by_hash_id >, member< primary_wallet_vout_object, fc::uint256, &primary_wallet_vout_object::hash_id > > + > +> primary_wallet_vout_multi_index_container; + +typedef generic_index primary_wallet_vout_index; + +} } + +FC_REFLECT_DERIVED( graphene::chain::primary_wallet_vout_object, (graphene::chain::object), (vout)(hash_id)(confirmed)(used) ) + diff --git a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp index bf6fc5472..0ca500962 100644 --- a/libraries/chain/include/graphene/chain/proposal_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/proposal_evaluator.hpp @@ -29,6 +29,25 @@ #include namespace graphene { namespace chain { + + class sidechain_hardfork_visitor + { + public: + typedef void result_type; + database& db; + proposal_id_type prop_id; + + sidechain_hardfork_visitor( database& _db, const proposal_id_type& _prop_id ) : db( _db ), prop_id( _prop_id ) {} + + template + void operator()( const T &v ) const {} + + void operator()( const bitcoin_transaction_send_operation &v ); + + void operator()( const bitcoin_issue_operation &v ); + + void operator()( const bitcoin_transaction_revert_operation &v ); + }; class proposal_create_evaluator : public evaluator { diff --git a/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp b/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp new file mode 100644 index 000000000..e55dc87bf --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/bitcoin_address.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct bitcoin_address_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type payer; + account_id_type owner; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + return k.fee; + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_address_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_address_create_operation, (fee)(payer)(owner) ) diff --git a/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp b/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp new file mode 100644 index 000000000..9115f4155 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/bitcoin_transaction.hpp @@ -0,0 +1,96 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct bitcoin_transaction_send_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + sidechain::info_for_vin pw_vin; + std::vector< sidechain::info_for_vin > vins; + std::vector< info_for_vout_id_type > vouts; + + sidechain::bitcoin_transaction transaction; + uint64_t fee_for_size; + + 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; + fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); + return fee_required; + } + }; + + struct bitcoin_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + proposal_id_type proposal_id; + std::vector signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct revert_trx_info + { + revert_trx_info() = default; + revert_trx_info( fc::sha256 trx_id, std::set< fc::sha256 > vins ): transaction_id( trx_id ), valid_vins( vins ) {} + + fc::sha256 transaction_id; + std::set< fc::sha256 > valid_vins; + + bool operator ==( const revert_trx_info& trx_info ){ + return this->transaction_id == trx_info.transaction_id && this->valid_vins == trx_info.valid_vins; + } + + bool operator !=( const revert_trx_info& trx_info ){ + return this->transaction_id != trx_info.transaction_id || this->valid_vins != trx_info.valid_vins; + } + }; + + struct bitcoin_transaction_revert_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + std::vector< revert_trx_info > transactions_info; + + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(pw_vin)(vins)(vouts)(transaction)(fee_for_size) ) + +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signatures) ) + +FC_REFLECT( graphene::chain::revert_trx_info, (transaction_id)(valid_vins) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation, (fee)(payer)(transactions_info) ) diff --git a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp index b2551e447..866427169 100644 --- a/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp +++ b/libraries/chain/include/graphene/chain/protocol/chain_parameters.hpp @@ -26,6 +26,7 @@ #include #include #include +#include namespace graphene { namespace chain { struct fee_schedule; } } @@ -37,6 +38,7 @@ namespace graphene { namespace chain { optional< uint16_t > betting_rake_fee_percentage; optional< flat_map > permitted_betting_odds_increments; optional< uint16_t > live_betting_delay_time; + optional< sidechain::sidechain_parameters_extension > sidechain_parameters; }; struct chain_parameters diff --git a/libraries/chain/include/graphene/chain/protocol/operations.hpp b/libraries/chain/include/graphene/chain/protocol/operations.hpp index 104a2ec38..f9fc8b739 100644 --- a/libraries/chain/include/graphene/chain/protocol/operations.hpp +++ b/libraries/chain/include/graphene/chain/protocol/operations.hpp @@ -44,6 +44,10 @@ #include #include #include +#include +#include +#include +#include namespace graphene { namespace chain { @@ -129,7 +133,13 @@ namespace graphene { namespace chain { sport_delete_operation, event_group_delete_operation, affiliate_payout_operation, // VIRTUAL - affiliate_referral_payout_operation // VIRTUAL + affiliate_referral_payout_operation, // VIRTUAL + withdraw_pbtc_operation, + bitcoin_address_create_operation, + bitcoin_transaction_send_operation, + bitcoin_transaction_sign_operation, + bitcoin_issue_operation, + bitcoin_transaction_revert_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 000000000..8475fef81 --- /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; + std::vector transaction_ids; + + + 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) ) +FC_REFLECT( graphene::chain::bitcoin_issue_operation, (fee)(payer)(transaction_ids) ) diff --git a/libraries/chain/include/graphene/chain/protocol/types.hpp b/libraries/chain/include/graphene/chain/protocol/types.hpp index 4b6e1589e..4b7b2b306 100644 --- a/libraries/chain/include/graphene/chain/protocol/types.hpp +++ b/libraries/chain/include/graphene/chain/protocol/types.hpp @@ -145,6 +145,12 @@ namespace graphene { namespace chain { betting_market_group_object_type, betting_market_object_type, bet_object_type, + info_for_vout_object_type, + info_for_used_vin_object_type, + bitcoin_address_object_type, + primary_wallet_vout_object_type, + sidechain_proposal_object_type, + bitcoin_transaction_object_type, OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types }; @@ -202,32 +208,44 @@ namespace graphene { namespace chain { class betting_market_group_object; class betting_market_object; class bet_object; + class info_for_vout_object; + class info_for_used_vin_object; + class bitcoin_address_object; + class primary_wallet_vout_object; + class sidechain_proposal_object; + class bitcoin_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; - typedef object_id< protocol_ids, force_settlement_object_type, force_settlement_object> force_settlement_id_type; - typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; - typedef object_id< protocol_ids, witness_object_type, witness_object> witness_id_type; - typedef object_id< protocol_ids, limit_order_object_type, limit_order_object> limit_order_id_type; - typedef object_id< protocol_ids, call_order_object_type, call_order_object> call_order_id_type; - typedef object_id< protocol_ids, custom_object_type, custom_object> custom_id_type; - typedef object_id< protocol_ids, proposal_object_type, proposal_object> proposal_id_type; - typedef object_id< protocol_ids, operation_history_object_type, operation_history_object> operation_history_id_type; - typedef object_id< protocol_ids, withdraw_permission_object_type,withdraw_permission_object> withdraw_permission_id_type; - typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; - typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; - typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; - typedef object_id< protocol_ids, tournament_object_type, tournament_object> tournament_id_type; - typedef object_id< protocol_ids, tournament_details_object_type, tournament_details_object> tournament_details_id_type; - typedef object_id< protocol_ids, match_object_type, match_object> match_id_type; - typedef object_id< protocol_ids, game_object_type, game_object> game_id_type; - typedef object_id< protocol_ids, sport_object_type, sport_object> sport_id_type; - typedef object_id< protocol_ids, event_group_object_type, event_group_object> event_group_id_type; - typedef object_id< protocol_ids, event_object_type, event_object> event_id_type; - typedef object_id< protocol_ids, betting_market_rules_object_type, betting_market_rules_object> betting_market_rules_id_type; - typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type; - typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type; - typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type; + 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; + typedef object_id< protocol_ids, force_settlement_object_type, force_settlement_object> force_settlement_id_type; + typedef object_id< protocol_ids, committee_member_object_type, committee_member_object> committee_member_id_type; + typedef object_id< protocol_ids, witness_object_type, witness_object> witness_id_type; + typedef object_id< protocol_ids, limit_order_object_type, limit_order_object> limit_order_id_type; + typedef object_id< protocol_ids, call_order_object_type, call_order_object> call_order_id_type; + typedef object_id< protocol_ids, custom_object_type, custom_object> custom_id_type; + typedef object_id< protocol_ids, proposal_object_type, proposal_object> proposal_id_type; + typedef object_id< protocol_ids, operation_history_object_type, operation_history_object> operation_history_id_type; + typedef object_id< protocol_ids, withdraw_permission_object_type, withdraw_permission_object> withdraw_permission_id_type; + typedef object_id< protocol_ids, vesting_balance_object_type, vesting_balance_object> vesting_balance_id_type; + typedef object_id< protocol_ids, worker_object_type, worker_object> worker_id_type; + typedef object_id< protocol_ids, balance_object_type, balance_object> balance_id_type; + typedef object_id< protocol_ids, tournament_object_type, tournament_object> tournament_id_type; + typedef object_id< protocol_ids, tournament_details_object_type, tournament_details_object> tournament_details_id_type; + typedef object_id< protocol_ids, match_object_type, match_object> match_id_type; + typedef object_id< protocol_ids, game_object_type, game_object> game_id_type; + typedef object_id< protocol_ids, sport_object_type, sport_object> sport_id_type; + typedef object_id< protocol_ids, event_group_object_type, event_group_object> event_group_id_type; + typedef object_id< protocol_ids, event_object_type, event_object> event_id_type; + typedef object_id< protocol_ids, betting_market_rules_object_type, betting_market_rules_object> betting_market_rules_id_type; + typedef object_id< protocol_ids, betting_market_group_object_type, betting_market_group_object> betting_market_group_id_type; + typedef object_id< protocol_ids, betting_market_object_type, betting_market_object> betting_market_id_type; + typedef object_id< protocol_ids, bet_object_type, bet_object> bet_id_type; + typedef object_id< protocol_ids, info_for_vout_object_type, info_for_vout_object> info_for_vout_id_type; + typedef object_id< protocol_ids, info_for_used_vin_object_type, info_for_used_vin_object> info_for_used_vin_id_type; + typedef object_id< protocol_ids, bitcoin_address_object_type, bitcoin_address_object> bitcoin_address_id_type; + typedef object_id< protocol_ids, primary_wallet_vout_object_type, primary_wallet_vout_object> primary_wallet_vout_id_type; + typedef object_id< protocol_ids, sidechain_proposal_object_type, sidechain_proposal_object> sidechain_proposal_id_type; + typedef object_id< protocol_ids, bitcoin_transaction_object_type, bitcoin_transaction_object> bitcoin_transaction_id_type; // implementation types class global_property_object; @@ -402,6 +420,12 @@ FC_REFLECT_ENUM( graphene::chain::object_type, (betting_market_group_object_type) (betting_market_object_type) (bet_object_type) + (info_for_vout_object_type) + (info_for_used_vin_object_type) + (bitcoin_address_object_type) + (primary_wallet_vout_object_type) + (sidechain_proposal_object_type) + (bitcoin_transaction_object_type) (OBJECT_TYPE_COUNT) ) FC_REFLECT_ENUM( graphene::chain::impl_object_type, @@ -453,6 +477,12 @@ FC_REFLECT_TYPENAME( graphene::chain::betting_market_group_id_type ) FC_REFLECT_TYPENAME( graphene::chain::betting_market_id_type ) FC_REFLECT_TYPENAME( graphene::chain::bet_id_type ) FC_REFLECT_TYPENAME( graphene::chain::tournament_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::info_for_vout_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::info_for_used_vin_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::bitcoin_address_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::primary_wallet_vout_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::sidechain_proposal_id_type ) +FC_REFLECT_TYPENAME( graphene::chain::bitcoin_transaction_id_type ) FC_REFLECT_TYPENAME( graphene::chain::global_property_id_type ) FC_REFLECT_TYPENAME( graphene::chain::dynamic_global_property_id_type ) FC_REFLECT_TYPENAME( graphene::chain::asset_dynamic_data_id_type ) diff --git a/libraries/chain/include/graphene/chain/protocol/withdraw_pbtc.hpp b/libraries/chain/include/graphene/chain/protocol/withdraw_pbtc.hpp new file mode 100644 index 000000000..567b2aac0 --- /dev/null +++ b/libraries/chain/include/graphene/chain/protocol/withdraw_pbtc.hpp @@ -0,0 +1,33 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct withdraw_pbtc_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + uint32_t price_per_kbyte = 10; + }; + + asset fee; + account_id_type payer; + + std::string data; // address or script + uint64_t amount; + + // object_id_type tx_obj_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; + fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); + return fee_required; + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::withdraw_pbtc_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::withdraw_pbtc_operation, (fee)(payer)(data)(amount) ) 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 000000000..a8c7787fa --- /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_identifier ); + + std::vector get_accounts_to_issue( std::vector vins_identifier ); +}; + +} } // graphene::chain \ No newline at end of file diff --git a/libraries/chain/include/graphene/chain/sidechain_proposal_object.hpp b/libraries/chain/include/graphene/chain/sidechain_proposal_object.hpp new file mode 100644 index 000000000..456e941ed --- /dev/null +++ b/libraries/chain/include/graphene/chain/sidechain_proposal_object.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { + +class sidechain_proposal_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = sidechain_proposal_object_type; + + sidechain_proposal_id_type get_id()const { return id; } + + proposal_id_type proposal_id; + sidechain::sidechain_proposal_type proposal_type; +}; + +struct by_proposal; +struct by_type; +typedef boost::multi_index_container< + sidechain_proposal_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_unique< tag< by_proposal >, member< sidechain_proposal_object, proposal_id_type, &sidechain_proposal_object::proposal_id > >, + ordered_non_unique< tag< by_type >, member< sidechain_proposal_object, sidechain::sidechain_proposal_type, &sidechain_proposal_object::proposal_type > > + > +> sidechain_multi_index_container; +typedef generic_index sidechain_proposal_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::sidechain_proposal_object, (graphene::chain::object), (proposal_id)(proposal_type) ) diff --git a/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp b/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp new file mode 100644 index 000000000..2812d675b --- /dev/null +++ b/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +using namespace sidechain; + +namespace graphene { namespace chain { + +class withdraw_pbtc_evaluator : public evaluator +{ +public: + typedef withdraw_pbtc_operation operation_type; + + void_result do_evaluate( const withdraw_pbtc_operation& op ); + + void_result do_apply( const withdraw_pbtc_operation& op ); + + void reserve_issue( const withdraw_pbtc_operation& op ); + + bool check_amount_higher_than_fee( const withdraw_pbtc_operation& op ); + + payment_type type; +}; + +} } // graphene::chain diff --git a/libraries/chain/proposal_evaluator.cpp b/libraries/chain/proposal_evaluator.cpp index a6640ea47..80d5e2c72 100644 --- a/libraries/chain/proposal_evaluator.cpp +++ b/libraries/chain/proposal_evaluator.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include @@ -142,6 +143,62 @@ struct proposal_operation_hardfork_visitor } }; +void sidechain_hardfork_visitor::operator()( const bitcoin_transaction_send_operation &v ) +{ + db.create([&]( sidechain_proposal_object& sch_prop ) { + sch_prop.proposal_type = sidechain::sidechain_proposal_type::SEND_BTC_TRANSACTION; + sch_prop.proposal_id = prop_id; + }); + + for( auto& vin : v.vins ) { + auto obj = db.i_w_info.find_info_for_vin( vin.identifier ); + if( obj.valid() ) + db.i_w_info.mark_as_used_vin( *obj ); + } + + for( auto& vout : v.vouts ) { + auto obj = db.i_w_info.find_info_for_vout( vout ); + FC_ASSERT( obj.valid(), "info_for_vout_object don't exist." ); + db.i_w_info.mark_as_used_vout( *obj ); + } + + db.pw_vout_manager.mark_as_used_vout( v.pw_vin.identifier ); +} + +void sidechain_hardfork_visitor::operator()( const bitcoin_issue_operation &v ) +{ + db.create([&]( sidechain_proposal_object& sch_prop ) { + sch_prop.proposal_type = sidechain::sidechain_proposal_type::ISSUE_BTC; + sch_prop.proposal_id = prop_id; + }); + + for( const auto& trx_id : v.transaction_ids ) { + const auto& trx_confirmations = db.bitcoin_confirmations.find( trx_id ); + if( trx_confirmations.valid() ) { + db.bitcoin_confirmations.modify( trx_id, [&]( sidechain::bitcoin_transaction_confirmations& obj ) { + obj.used = true; + }); + } + } +} + +void sidechain_hardfork_visitor::operator()( const bitcoin_transaction_revert_operation &v ) +{ + db.create([&]( sidechain_proposal_object& sch_prop ) { + sch_prop.proposal_type = sidechain::sidechain_proposal_type::REVERT_BTC_TRANSACTION; + sch_prop.proposal_id = prop_id; + }); + + for( const auto& trx : v.transactions_info ) { + const auto& trx_confirmations = db.bitcoin_confirmations.find( trx.transaction_id ); + if( trx_confirmations.valid() ) { + db.bitcoin_confirmations.modify( trx.transaction_id, [&]( sidechain::bitcoin_transaction_confirmations& obj ) { + obj.used = true; + }); + } + } +} + void_result proposal_create_evaluator::do_evaluate(const proposal_create_operation& o) { try { const database& d = db(); @@ -157,6 +214,14 @@ void_result proposal_create_evaluator::do_evaluate(const proposal_create_operati FC_ASSERT( !o.review_period_seconds || fc::seconds(*o.review_period_seconds) < (o.expiration_time - d.head_block_time()), "Proposal review period must be less than its overall lifetime." ); + bool pbtc_op = ( o.proposed_ops[0].op.which() == operation::tag::value ) || + ( o.proposed_ops[0].op.which() == operation::tag::value ) || + ( o.proposed_ops[0].op.which() == operation::tag::value ); + + if( d.is_sidechain_fork_needed() && pbtc_op ) { + FC_THROW( "Currently the operation is unavailable." ); + } + { // If we're dealing with the committee authority, make sure this transaction has a sufficient review period. flat_set auths; @@ -220,6 +285,9 @@ object_id_type proposal_create_evaluator::do_apply(const proposal_create_operati std::inserter(proposal.required_active_approvals, proposal.required_active_approvals.begin())); }); + sidechain_hardfork_visitor sidechain_vtor( d , proposal.id ); + o.proposed_ops[0].op.visit( sidechain_vtor ); + return proposal.id; } FC_CAPTURE_AND_RETHROW( (o) ) } diff --git a/libraries/chain/sidechain_evaluator.cpp b/libraries/chain/sidechain_evaluator.cpp new file mode 100644 index 000000000..b084269f0 --- /dev/null +++ b/libraries/chain/sidechain_evaluator.cpp @@ -0,0 +1,141 @@ +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result bitcoin_issue_evaluator::do_evaluate( const bitcoin_issue_operation& op ) +{ try { + 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() ); + + for( const auto& id: op.transaction_ids ) { + const auto& btc_itr = btc_trx_idx.find( id ); + FC_ASSERT( btc_itr != btc_trx_idx.end() ); + + for( auto& vin_id : btc_itr->vins ) { + const auto& itr = vins_info_idx.find( vin_id ); + FC_ASSERT( itr != vins_info_idx.end() ); + auto addr_itr = btc_addr_idx.find( itr->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(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +void_result bitcoin_issue_evaluator::do_apply( const bitcoin_issue_operation& op ) +{ try { + database& d = db(); + const auto& btc_trx_idx = d.get_index_type().indices().get(); + + for( const auto& id: op.transaction_ids ) { + const auto& btc_obj = *btc_trx_idx.find( id ); + add_issue( btc_obj ); + + d.pw_vout_manager.confirm_vout( btc_obj.pw_vin ); + clear_btc_transaction_information( btc_obj ); + } + + return void_result(); +} FC_CAPTURE_AND_RETHROW( (op) ) } + +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 ); + + uint64_t fee_deduction = btc_obj.fee_for_size / ( btc_obj.vins.size() + btc_obj.vouts.size() ); + + if( btc_obj.fee_for_size % ( btc_obj.vins.size() + btc_obj.vouts.size() ) != 0 ) { + fee_deduction += 1; + } + + 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] - fee_deduction, 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_identifier ) +{ + database& d = db(); + const auto& vins_info_idx = d.get_index_type().indices().get(); + + std::vector result; + + for( auto& identifier : vins_identifier ) { + auto vin_itr = vins_info_idx.find( identifier ); + result.push_back( vin_itr->out.amount ); + } + + return result; +} + +std::vector bitcoin_issue_evaluator::get_accounts_to_issue( std::vector vins_identifier ) +{ + 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& identifier : vins_identifier ) { + auto vin_itr = vins_info_idx.find( identifier ); + 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/libraries/chain/withdraw_pbtc_evaluator.cpp b/libraries/chain/withdraw_pbtc_evaluator.cpp new file mode 100644 index 000000000..b1b517fd4 --- /dev/null +++ b/libraries/chain/withdraw_pbtc_evaluator.cpp @@ -0,0 +1,85 @@ +#include +#include +#include + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +void_result withdraw_pbtc_evaluator::do_evaluate(const withdraw_pbtc_operation& op) +{ + database& d = db(); + + FC_ASSERT( !d.is_sidechain_fork_needed() ); + FC_ASSERT( op.data.size() > 0 ); + type = bitcoin_address( op.data ).get_type(); + FC_ASSERT( type != payment_type::NULLDATA , "Invalid address type." ); + FC_ASSERT( check_amount_higher_than_fee( op ) ); + asset acc_balance = db().get_balance( op.payer, d.get_sidechain_asset_id() ); + FC_ASSERT( acc_balance.amount.value >= op.amount ); + + return void_result(); +} + +void_result withdraw_pbtc_evaluator::do_apply(const withdraw_pbtc_operation& op) +{ + db().i_w_info.insert_info_for_vout( op.payer, op.data, op.amount ); + reserve_issue( op ); + return void_result(); +} + +void withdraw_pbtc_evaluator::reserve_issue( const withdraw_pbtc_operation& op ) +{ + database& d = db(); + + 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; + + asset_reserve_operation reserve_op; + reserve_op.amount_to_reserve = asset( op.amount, d.get_sidechain_asset_id() ); + reserve_op.payer = op.payer; + + d.apply_operation( *trx_state, reserve_op ); + + trx_state->skip_fee = skip_fee_old; + trx_state->skip_fee_schedule_check = skip_fee_schedule_check_old; +} + +bool withdraw_pbtc_evaluator::check_amount_higher_than_fee( const withdraw_pbtc_operation& op ) { + database& d = db(); + + info_for_vout_object obj; + obj.payer = op.payer; + obj.address = op.data; + obj.amount = op.amount; + obj.used = false; + + const auto& pw_address = d.get_latest_PW().address; + + sidechain::info_for_vin pw_vin; + pw_vin.identifier = fc::sha256( std::string(64, '1') ); + pw_vin.out.hash_tx = std::string(64, '1'); + pw_vin.out.n_vout = 0; + pw_vin.out.amount = 2*op.amount; + pw_vin.address = pw_address.get_address(); + pw_vin.script = pw_address.get_witness_script(); + + const auto& mock_trx = d.create_btc_transaction( {}, { obj }, pw_vin ); + + if( op.amount < mock_trx.second ) + return false; + + uint64_t fee_for_witnesses = ( (op.amount - mock_trx.second) * d.get_sidechain_params().percent_payment_to_witnesses ) / GRAPHENE_100_PERCENT; + + if( op.amount < mock_trx.second + fee_for_witnesses ) + return false; + + return true; +} + +} } // namespace graphene::chain diff --git a/libraries/net/CMakeLists.txt b/libraries/net/CMakeLists.txt index 39f9cd05c..35a12eae0 100644 --- a/libraries/net/CMakeLists.txt +++ b/libraries/net/CMakeLists.txt @@ -10,7 +10,7 @@ set(SOURCES node.cpp add_library( graphene_net ${SOURCES} ${HEADERS} ) target_link_libraries( graphene_net - PUBLIC fc graphene_db ) + PUBLIC fc graphene_db sidechain ) target_include_directories( graphene_net PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../chain/include" diff --git a/libraries/plugins/witness/CMakeLists.txt b/libraries/plugins/witness/CMakeLists.txt index 95759bbf2..51d03ba7f 100644 --- a/libraries/plugins/witness/CMakeLists.txt +++ b/libraries/plugins/witness/CMakeLists.txt @@ -4,7 +4,7 @@ add_library( graphene_witness witness.cpp ) -target_link_libraries( graphene_witness graphene_chain graphene_app ) +target_link_libraries( graphene_witness graphene_chain graphene_app graphene_time sidechain_network ) target_include_directories( graphene_witness PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/plugins/witness/include/graphene/witness/witness.hpp b/libraries/plugins/witness/include/graphene/witness/witness.hpp index e2f60bf8d..d8152793a 100644 --- a/libraries/plugins/witness/include/graphene/witness/witness.hpp +++ b/libraries/plugins/witness/include/graphene/witness/witness.hpp @@ -25,6 +25,7 @@ #include #include +#include #include @@ -82,6 +83,7 @@ class witness_plugin : public graphene::app::plugin { bool _consecutive_production_enabled = false; uint32_t _required_witness_participation = 33 * GRAPHENE_1_PERCENT; uint32_t _production_skip_flags = graphene::chain::database::skip_nothing; + sidechain::sidechain_net_manager bitcoin_manager; std::map _private_keys; std::set _witnesses; diff --git a/libraries/plugins/witness/witness.cpp b/libraries/plugins/witness/witness.cpp index dce1234a7..b4f08898d 100644 --- a/libraries/plugins/witness/witness.cpp +++ b/libraries/plugins/witness/witness.cpp @@ -121,6 +121,19 @@ void witness_plugin::plugin_initialize(const boost::program_options::variables_m _private_keys[key_id_to_wif_pair.first] = *private_key; } } + if( 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" ) ) + { + const auto ip = options.at("bitcoin-node-ip").as(); + const auto zmq_port = options.at("bitcoin-node-zmq-port").as(); + const auto rpc_port = options.at("bitcoin-node-rpc-port").as(); + const auto rpc_user = options.at("bitcoin-node-rpc-user").as(); + const auto rpc_password = options.at("bitcoin-node-rpc-password").as(); + + bitcoin_manager.initialize_manager(&database(), ip, zmq_port, rpc_port, rpc_user, rpc_password); + } else { + wlog("Haven't set up sidechain parameters"); + } ilog("witness plugin: plugin_initialize() end"); } FC_LOG_AND_RETHROW() } diff --git a/libraries/sidechain/CMakeLists.txt b/libraries/sidechain/CMakeLists.txt new file mode 100644 index 000000000..515acbe24 --- /dev/null +++ b/libraries/sidechain/CMakeLists.txt @@ -0,0 +1,9 @@ +file( GLOB SOURCES "*.cpp" ) +file( GLOB HEADERS "include/*.hpp" ) + +add_subdirectory( network ) + +add_library( sidechain STATIC ${SOURCES} ${HEADERS} ) + +target_link_libraries( sidechain fc graphene_chain ) +target_include_directories( sidechain PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/sidechain/bech32.cpp b/libraries/sidechain/bech32.cpp new file mode 100644 index 000000000..c861169db --- /dev/null +++ b/libraries/sidechain/bech32.cpp @@ -0,0 +1,192 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +#include + +// #include + +namespace +{ + +typedef std::vector data; + +/** The Bech32 character set for encoding. */ +const char* CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + +/** The Bech32 character set for decoding. */ +const int8_t CHARSET_REV[128] = { + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, + 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, + -1, 29, -1, 24, 13, 25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, + 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 +}; + +/** Concatenate two byte arrays. */ +data Cat(data x, const data& y) +{ + x.insert(x.end(), y.begin(), y.end()); + return x; +} + +/** This function will compute what 6 5-bit values to XOR into the last 6 input values, in order to + * make the checksum 0. These 6 values are packed together in a single 30-bit integer. The higher + * bits correspond to earlier values. */ +uint32_t PolyMod(const data& v) +{ + // The input is interpreted as a list of coefficients of a polynomial over F = GF(32), with an + // implicit 1 in front. If the input is [v0,v1,v2,v3,v4], that polynomial is v(x) = + // 1*x^5 + v0*x^4 + v1*x^3 + v2*x^2 + v3*x + v4. The implicit 1 guarantees that + // [v0,v1,v2,...] has a distinct checksum from [0,v0,v1,v2,...]. + + // The output is a 30-bit integer whose 5-bit groups are the coefficients of the remainder of + // v(x) mod g(x), where g(x) is the Bech32 generator, + // x^6 + {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18}. g(x) is chosen in such a way + // that the resulting code is a BCH code, guaranteeing detection of up to 3 errors within a + // window of 1023 characters. Among the various possible BCH codes, one was selected to in + // fact guarantee detection of up to 4 errors within a window of 89 characters. + + // Note that the coefficients are elements of GF(32), here represented as decimal numbers + // between {}. In this finite field, addition is just XOR of the corresponding numbers. For + // example, {27} + {13} = {27 ^ 13} = {22}. Multiplication is more complicated, and requires + // treating the bits of values themselves as coefficients of a polynomial over a smaller field, + // GF(2), and multiplying those polynomials mod a^5 + a^3 + 1. For example, {5} * {26} = + // (a^2 + 1) * (a^4 + a^3 + a) = (a^4 + a^3 + a) * a^2 + (a^4 + a^3 + a) = a^6 + a^5 + a^4 + a + // = a^3 + 1 (mod a^5 + a^3 + 1) = {9}. + + // During the course of the loop below, `c` contains the bitpacked coefficients of the + // polynomial constructed from just the values of v that were processed so far, mod g(x). In + // the above example, `c` initially corresponds to 1 mod (x), and after processing 2 inputs of + // v, it corresponds to x^2 + v0*x + v1 mod g(x). As 1 mod g(x) = 1, that is the starting value + // for `c`. + uint32_t c = 1; + for (auto v_i : v) { + // We want to update `c` to correspond to a polynomial with one extra term. If the initial + // value of `c` consists of the coefficients of c(x) = f(x) mod g(x), we modify it to + // correspond to c'(x) = (f(x) * x + v_i) mod g(x), where v_i is the next input to + // process. Simplifying: + // c'(x) = (f(x) * x + v_i) mod g(x) + // ((f(x) mod g(x)) * x + v_i) mod g(x) + // (c(x) * x + v_i) mod g(x) + // If c(x) = c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5, we want to compute + // c'(x) = (c0*x^5 + c1*x^4 + c2*x^3 + c3*x^2 + c4*x + c5) * x + v_i mod g(x) + // = c0*x^6 + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i mod g(x) + // = c0*(x^6 mod g(x)) + c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i + // If we call (x^6 mod g(x)) = k(x), this can be written as + // c'(x) = (c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i) + c0*k(x) + + // First, determine the value of c0: + uint8_t c0 = c >> 25; + + // Then compute c1*x^5 + c2*x^4 + c3*x^3 + c4*x^2 + c5*x + v_i: + c = ((c & 0x1ffffff) << 5) ^ v_i; + + // Finally, for each set bit n in c0, conditionally add {2^n}k(x): + if (c0 & 1) c ^= 0x3b6a57b2; // k(x) = {29}x^5 + {22}x^4 + {20}x^3 + {21}x^2 + {29}x + {18} + if (c0 & 2) c ^= 0x26508e6d; // {2}k(x) = {19}x^5 + {5}x^4 + x^3 + {3}x^2 + {19}x + {13} + if (c0 & 4) c ^= 0x1ea119fa; // {4}k(x) = {15}x^5 + {10}x^4 + {2}x^3 + {6}x^2 + {15}x + {26} + if (c0 & 8) c ^= 0x3d4233dd; // {8}k(x) = {30}x^5 + {20}x^4 + {4}x^3 + {12}x^2 + {30}x + {29} + if (c0 & 16) c ^= 0x2a1462b3; // {16}k(x) = {21}x^5 + x^4 + {8}x^3 + {24}x^2 + {21}x + {19} + } + return c; +} + +/** Convert to lower case. */ +inline unsigned char LowerCase(unsigned char c) +{ + return (c >= 'A' && c <= 'Z') ? (c - 'A') + 'a' : c; +} + +/** Expand a HRP for use in checksum computation. */ +data ExpandHRP(const std::string& hrp) +{ + data ret; + ret.reserve(hrp.size() + 90); + 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; +} + +/** Verify a checksum. */ +bool VerifyChecksum(const std::string& hrp, const data& values) +{ + // PolyMod computes what value to xor into the final values to make the checksum 0. However, + // if we required that the checksum was 0, it would be the case that appending a 0 to a valid + // list of values would result in a new valid list. For that reason, Bech32 requires the + // resulting checksum to be 1 instead. + return PolyMod(Cat(ExpandHRP(hrp), values)) == 1; +} + +/** Create a checksum. */ +data CreateChecksum(const std::string& hrp, const data& values) +{ + data enc = Cat(ExpandHRP(hrp), values); + enc.resize(enc.size() + 6); // Append 6 zeroes + uint32_t mod = PolyMod(enc) ^ 1; // Determine what to XOR into those 6 zeroes. + data ret(6); + for (size_t i = 0; i < 6; ++i) { + // Convert the 5-bit groups in mod to checksum values. + ret[i] = (mod >> (5 * (5 - i))) & 31; + } + return ret; +} + +} // namespace + +namespace sidechain { namespace bech32 { + +/** Encode a Bech32 string. */ +std::string Encode(const std::string& hrp, const data& values) { + data checksum = CreateChecksum(hrp, values); + data combined = Cat(values, checksum); + std::string ret = hrp + '1'; + ret.reserve(ret.size() + combined.size()); + for (auto c : combined) { + ret += CHARSET[c]; + } + return ret; +} + +/** Decode a Bech32 string. */ +std::pair Decode(const std::string& str) { + bool lower = false, upper = false; + for (size_t i = 0; i < str.size(); ++i) { + unsigned char c = str[i]; + if (c < 33 || c > 126) return {}; + if (c >= 'a' && c <= 'z') lower = true; + if (c >= 'A' && c <= 'Z') upper = true; + } + if (lower && upper) return {}; + size_t pos = str.rfind('1'); + if (str.size() > 90 || pos == str.npos || pos == 0 || pos + 7 > str.size()) { + return {}; + } + data values(str.size() - 1 - pos); + for (size_t i = 0; i < str.size() - 1 - pos; ++i) { + unsigned char c = str[i + pos + 1]; + int8_t rev = (c < 33 || c > 126) ? -1 : CHARSET_REV[c]; + if (rev == -1) { + return {}; + } + values[i] = rev; + } + std::string hrp; + for (size_t i = 0; i < pos; ++i) { + hrp += LowerCase(str[i]); + } + if (!VerifyChecksum(hrp, values)) { + return {}; + } + return {hrp, data(values.begin(), values.end() - 6)}; +} + +} } // namespace sidechain::bech32 diff --git a/libraries/sidechain/bitcoin_address.cpp b/libraries/sidechain/bitcoin_address.cpp new file mode 100644 index 000000000..1ebab95b4 --- /dev/null +++ b/libraries/sidechain/bitcoin_address.cpp @@ -0,0 +1,258 @@ +#include +#include +#include +#include +#include + +namespace sidechain { + +bool bitcoin_address::operator==( const bitcoin_address& btc_addr ) const { + return ( this->address == btc_addr.address ) && + ( this->type == btc_addr.type ) && + ( this->raw_address == btc_addr.raw_address ); +} + +bytes bitcoin_address::get_script() const +{ + switch ( type ) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << raw_address; + case payment_type::P2PK: + return script_builder() << raw_address << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << raw_address << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << raw_address << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << raw_address; + default: + return raw_address; + } +} + +payment_type bitcoin_address::determine_type() +{ + if( is_p2pk() ) { + return payment_type::P2PK; + } else if( is_p2wpkh() ) { + return payment_type::P2WPKH; + } else if( is_p2wsh() ) { + return payment_type::P2WSH; + } else if( is_p2pkh() ) { + return payment_type::P2PKH; + } else if( is_p2sh() ) { + return payment_type::P2SH; + } else { + return payment_type::NULLDATA; + } +} + +bytes bitcoin_address::determine_raw_address() +{ + bytes result; + switch( type ) { + case payment_type::P2PK : { + result = parse_hex( address ); + break; + } + case payment_type::P2WPKH : + case payment_type::P2WSH : { + std::string prefix( address.compare(0,4,"bcrt") == 0 ? std::string( address.begin(), address.begin() + 4 ) : + std::string( address.begin(), address.begin() + 2 ) ); + const auto& decode_bech32 = segwit_addr::decode( prefix, address ); + result = bytes( decode_bech32.second.begin(), decode_bech32.second.end() ); + break; + } + case payment_type::P2SH_WPKH : + case payment_type::P2SH_WSH : + case payment_type::P2PKH : + case payment_type::P2SH : { + bytes hex_addr = fc::from_base58( address ); + result = bytes( hex_addr.begin() + 1, hex_addr.begin() + 21 ); + break; + } + case payment_type::NULLDATA : return result; + } + return result; +} + +bool bitcoin_address::check_segwit_address( const size_segwit_address& size ) const { + if( !address.compare(0,4,"bcrt") || !address.compare(0,2,"bc") || !address.compare(0,2,"tb") ) { + std::string prefix( !address.compare(0,4,"bcrt") ? std::string(address.begin(), address.begin() + 4) : + std::string(address.begin(), address.begin() + 2) ); + + const auto& decode_bech32 = segwit_addr::decode( prefix, address ); + + if( decode_bech32.first == -1 || decode_bech32.second.size() != size ) { + return false; + } + + return true; + } + return false; +} + +bool bitcoin_address::is_p2pk() const +{ + try { + bool prefix = !address.compare(0,2,"02") || !address.compare(0,2,"03"); + if( address.size() == 66 && prefix ) { + parse_hex( address ); + return true; + } + } catch( fc::exception e ) { + return false; + } + return false; +} + +bool bitcoin_address::is_p2wpkh() const +{ + return check_segwit_address( size_segwit_address::P2WPKH ); +} + +bool bitcoin_address::is_p2wsh() const +{ + return check_segwit_address( size_segwit_address::P2WSH ); +} + +bool bitcoin_address::is_p2pkh() const +{ + try { + bytes hex_addr = fc::from_base58( address ); + if( hex_addr.size() == 25 && ( static_cast( hex_addr[0] ) == 0x00 || + static_cast( hex_addr[0] ) == 0x6f ) ) { + return true; + } + return false; + } catch( fc::exception e ) { + return false; + } +} + +bool bitcoin_address::is_p2sh() const +{ + try { + bytes hex_addr = fc::from_base58( address ); + if( hex_addr.size() == 25 && ( static_cast( hex_addr[0] ) == 0x05 || + static_cast( hex_addr[0] ) == 0xc4 ) ) { + return true; + } + return false; + } catch( fc::exception e ) { + return false; + } +} + +btc_multisig_address::btc_multisig_address( const size_t n_required, const accounts_keys& keys ) : + keys_required ( n_required ), witnesses_keys( keys ) +{ + create_redeem_script(); + create_address(); + type = payment_type::P2SH; +} + +size_t btc_multisig_address::count_intersection( const accounts_keys& keys ) const +{ + FC_ASSERT( keys.size() > 0 ); + + int intersections_count = 0; + for( auto& key : keys ) { + auto witness_key = witnesses_keys.find( key.first ); + if( witness_key == witnesses_keys.end() ) continue; + if( key.second == witness_key->second ) + intersections_count++; + } + return intersections_count; +} + +void btc_multisig_address::create_redeem_script() +{ + FC_ASSERT( keys_required > 0 ); + FC_ASSERT( keys_required < witnesses_keys.size() ); + redeem_script.clear(); + redeem_script.push_back( op_num[keys_required - 1] ); + for( const auto& key : witnesses_keys ) { + std::stringstream ss; + ss << std::hex << key.second.key_data.size(); + auto key_size_hex = sidechain::parse_hex( ss.str() ); + redeem_script.insert( redeem_script.end(), key_size_hex.begin(), key_size_hex.end() ); + redeem_script.insert( redeem_script.end(), key.second.key_data.begin(), key.second.key_data.end() ); + } + redeem_script.push_back( op_num[witnesses_keys.size() - 1] ); + redeem_script.push_back( OP_CHECKMULTISIG ); +} + +void btc_multisig_address::create_address() +{ + FC_ASSERT( redeem_script.size() > 0 ); + raw_address.clear(); + fc::sha256 hash256 = fc::sha256::hash( redeem_script.data(), redeem_script.size() ); + fc::ripemd160 hash160 = fc::ripemd160::hash( hash256.data(), hash256.data_size() ); + bytes temp_addr_hash( sidechain::parse_hex( hash160.str() ) ); + + raw_address.push_back( OP_HASH160 ); + std::stringstream ss; + ss << std::hex << temp_addr_hash.size(); + auto address_size_hex = sidechain::parse_hex( ss.str() ); + raw_address.insert( raw_address.end(), address_size_hex.begin(), address_size_hex.end() ); + raw_address.insert( raw_address.end(), temp_addr_hash.begin(), temp_addr_hash.end() ); + raw_address.push_back( OP_EQUAL ); +} + +btc_multisig_segwit_address::btc_multisig_segwit_address( const size_t n_required, const accounts_keys& keys ) : + btc_multisig_address( n_required, keys ) +{ + create_witness_script(); + create_segwit_address(); + type = payment_type::P2SH; +} + +bool btc_multisig_segwit_address::operator==( const btc_multisig_segwit_address& addr ) const +{ + if( address != addr.address || redeem_script != addr.redeem_script || + witnesses_keys != addr.witnesses_keys || witness_script != addr.witness_script || + raw_address != addr.raw_address ) + return false; + return true; +} + +std::vector btc_multisig_segwit_address::get_keys() { + std::vector keys; + for( const auto& k : witnesses_keys ) { + keys.push_back( k.second ); + } + return keys; +} + +void btc_multisig_segwit_address::create_witness_script() +{ + const auto redeem_sha256 = fc::sha256::hash( redeem_script.data(), redeem_script.size() ); + witness_script.push_back( OP_0 ); + witness_script.push_back( 0x20 ); // PUSH_32 + witness_script.insert( witness_script.end(), redeem_sha256.data(), redeem_sha256.data() + redeem_sha256.data_size() ); +} + +void btc_multisig_segwit_address::create_segwit_address() +{ + fc::sha256 hash256 = fc::sha256::hash( witness_script.data(), witness_script.size() ); + fc::ripemd160 hash160 = fc::ripemd160::hash( hash256.data(), hash256.data_size() ); + + raw_address = bytes(hash160.data(), hash160.data() + hash160.data_size() ); + address = fc::to_base58( get_address_bytes( raw_address ) ); +} + +bytes btc_multisig_segwit_address::get_address_bytes( const bytes& script_hash ) +{ + bytes address_bytes( 1, TESTNET_SCRIPT ); // 1 byte version + address_bytes.insert( address_bytes.end(), script_hash.begin(), script_hash.end() ); + fc::sha256 hash256 = fc::sha256::hash( fc::sha256::hash( address_bytes.data(), address_bytes.size() ) ); + address_bytes.insert( address_bytes.end(), hash256.data(), hash256.data() + 4 ); // 4 byte checksum + + return address_bytes; +} + +} diff --git a/libraries/sidechain/bitcoin_script.cpp b/libraries/sidechain/bitcoin_script.cpp new file mode 100644 index 000000000..367e5ff8e --- /dev/null +++ b/libraries/sidechain/bitcoin_script.cpp @@ -0,0 +1,60 @@ +#include +#include + +namespace sidechain { + +script_builder& script_builder::operator<<( op opcode ) +{ + const auto op_byte = static_cast( opcode ); + if ( op_byte < 0 || op_byte > 0xff ) + throw std::runtime_error( "script_builder::operator<<(OP): invalid opcode" ); + script.push_back( op_byte ); + return *this; +} + +script_builder& script_builder::operator<<( uint8_t number ) +{ + FC_ASSERT( 0 <= number && number <= 16 ); + + if ( number == 0 ) + script.push_back( static_cast( op::_0 ) ); + else + script.push_back( static_cast( op::_1 ) + number - 1 ); + + return *this; +} + +script_builder& script_builder::operator<<( size_t size ) +{ + sidechain::write_compact_size( script, size ); + return *this; +} + +script_builder& script_builder::operator<<( const bytes& sc ) { + sidechain::write_compact_size( script, sc.size() ); + script.insert( script.end(), sc.begin(), sc.end() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::sha256& hash ) +{ + sidechain::write_compact_size( script, hash.data_size() ); + script.insert( script.end(), hash.data(), hash.data() + hash.data_size() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::ripemd160& hash ) +{ + sidechain::write_compact_size( script, hash.data_size() ); + script.insert( script.end(), hash.data(), hash.data() + hash.data_size() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::ecc::public_key_data& pubkey_data ) +{ + sidechain::write_compact_size( script, pubkey_data.size() ); + script.insert( script.end(), pubkey_data.begin(), pubkey_data.begin() + pubkey_data.size() ); + return *this; +} + +} diff --git a/libraries/sidechain/bitcoin_transaction.cpp b/libraries/sidechain/bitcoin_transaction.cpp new file mode 100644 index 000000000..e099cd3f5 --- /dev/null +++ b/libraries/sidechain/bitcoin_transaction.cpp @@ -0,0 +1,279 @@ +#include +#include +#include +#include + +namespace sidechain { + +bool out_point::operator==( const out_point& op ) const +{ + if( this->hash == op.hash && + this->n == op.n ) + { + return true; + } + return false; +} + +bool tx_in::operator==( const tx_in& ti ) const +{ + if( this->prevout == ti.prevout && + this->scriptSig == ti.scriptSig && + this->nSequence == ti.nSequence ) + { + return true; + } + return false; +} + +bool tx_out::operator==( const tx_out& to ) const +{ + if( this->value == to.value && + this->scriptPubKey == to.scriptPubKey ) + { + return true; + } + return false; +} + +bool tx_out::is_p2wsh() const +{ + if( scriptPubKey.size() == 34 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x20) ) { + return true; + } + return false; +} + +bool tx_out::is_p2wpkh() const +{ + if( scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x14) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pkh() const +{ + if( scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast(0x76) && scriptPubKey[1] == static_cast(0xa9) && + scriptPubKey[2] == static_cast(0x14) && scriptPubKey[23] == static_cast(0x88) && scriptPubKey[24] == static_cast(0xac) ) { + return true; + } + return false; +} + +bool tx_out::is_p2sh() const +{ + if( scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast(0xa9) && + scriptPubKey[1] == static_cast(0x14) && scriptPubKey[22] == static_cast(0x87) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pk() const +{ + if( scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast(0x21) && scriptPubKey[34] == static_cast(0xac) ) { + return true; + } + return false; +} + +bytes tx_out::get_data_or_script() const +{ + if( is_p2pkh() ) { + return bytes( scriptPubKey.begin() + 3, scriptPubKey.begin() + 23 ); + } else if( is_p2sh() || is_p2wpkh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 22 ); + } else if( is_p2wsh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 34 ); + } else if( is_p2pk() ) { + return bytes( scriptPubKey.begin() + 1, scriptPubKey.begin() + 34 ); + } + return scriptPubKey; +} + +bool bitcoin_transaction::operator!=( const bitcoin_transaction& bt ) const +{ + if( this->nVersion != bt.nVersion || + this->vin != bt.vin || + this->vout != bt.vout || + this->nLockTime != bt.nLockTime ) + { + return true; + } + return false; +} + +fc::sha256 bitcoin_transaction::get_hash() const +{ + const auto bytes = pack( *this, true) ; + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +fc::sha256 bitcoin_transaction::get_txid() const +{ + const auto bytes = pack( *this, false ); + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +size_t bitcoin_transaction::get_vsize() const +{ + static const auto witness_scale_factor = 4; + + fc::datastream no_wit_ds; + pack( no_wit_ds, *this, false ); + + fc::datastream wit_ds; + pack( wit_ds, *this, true ); + + const size_t weight = no_wit_ds.tellp() * ( witness_scale_factor - 1 ) + wit_ds.tellp(); + const size_t vsize = ( weight + witness_scale_factor - 1 ) / witness_scale_factor; + + return vsize; +} + +void bitcoin_transaction_builder::set_version( int32_t version ) +{ + tx.nVersion = version; +} + +void bitcoin_transaction_builder::set_locktime(uint32_t lock_time) +{ + tx.nLockTime = lock_time; +} + +void bitcoin_transaction_builder::add_in( payment_type type, const fc::sha256& txid, uint32_t n_out, const bytes& script_code, bool front, uint32_t sequence ) +{ + out_point prevout; + prevout.hash = txid; + prevout.n = n_out; + + tx_in txin; + txin.prevout = prevout; + txin.nSequence = sequence; + + add_in( type, txin, script_code, front ); +} + +void bitcoin_transaction_builder::add_in( payment_type type, tx_in txin, const bytes& script_code, bool front ) +{ + switch ( type ) { + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + break; + default:{ + if( txin.prevout.hash == fc::sha256("0000000000000000000000000000000000000000000000000000000000000000") ) { //coinbase + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + } + break; + } + } + + if( front ) { + tx.vin.insert( tx.vin.begin(), txin ); + } else { + tx.vin.push_back( txin ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const std::string& base58_address, bool front ) +{ + // TODO: add checks + const auto address_bytes = fc::from_base58(base58_address); + add_out( type, amount, bytes( address_bytes.begin() + 1, address_bytes.begin() + 1 + 20 ), front ); +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const fc::ecc::public_key_data& pubkey, bool front ) +{ + FC_ASSERT( is_payment_to_pubkey( type ) ); + + if ( type == payment_type::P2PK ) { + const auto pubkey_bytes = bytes( pubkey.begin(), pubkey.begin() + pubkey.size() ); + add_out( type, amount, pubkey_bytes, front ); + } else { + const auto hash256 = fc::sha256::hash( pubkey.begin(), pubkey.size() ); + const auto hash160 = fc::ripemd160::hash( hash256.data(), hash256.data_size() ); + add_out( type, amount, bytes( hash160.data(), hash160.data() + hash160.data_size() ), front ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const bytes& script_code, bool front ) +{ + tx_out out; + out.value = amount; + out.scriptPubKey = get_script_pubkey( type, script_code ); + + if( front ) { + tx.vout.insert( tx.vout.begin(), out ); + } else { + tx.vout.push_back( out ); + } +} + +void bitcoin_transaction_builder::add_out_all_type( const uint64_t& amount, const bitcoin_address& address, bool front ) +{ + switch( address.get_type() ) { + case payment_type::P2PK: { + bytes raw_address( address.get_raw_address() ); + fc::ecc::public_key_data public_key; + std::copy( raw_address.begin(), raw_address.end(), public_key.begin() ); + add_out( address.get_type(), amount, public_key, front ); + break; + } + case payment_type::P2PKH: + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: { + add_out( address.get_type(), amount, address.get_address(), front ); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + add_out( address.get_type(), amount, address.get_raw_address(), front ); + break; + } + } +} + +inline bool bitcoin_transaction_builder::is_payment_to_pubkey( payment_type type ) +{ + switch ( type ) { + case payment_type::P2PK: + case payment_type::P2PKH: + case payment_type::P2WPKH: + case payment_type::P2SH_WPKH: + return true; + default: + return false; + } +} + +bytes bitcoin_transaction_builder::get_script_pubkey( payment_type type, const bytes& script_code ) +{ + switch ( type ) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << script_code; + case payment_type::P2PK: + return script_builder() << script_code << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << script_code << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << script_code << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << script_code; + default: + return script_code; + } +} + +} diff --git a/libraries/sidechain/include/sidechain/bech32.hpp b/libraries/sidechain/include/sidechain/bech32.hpp new file mode 100644 index 000000000..d5361312d --- /dev/null +++ b/libraries/sidechain/include/sidechain/bech32.hpp @@ -0,0 +1,24 @@ +// Copyright (c) 2017 Pieter Wuille +// Distributed under the MIT software license, see the accompanying +// file COPYING or http://www.opensource.org/licenses/mit-license.php. + +// Bech32 is a string encoding format used in newer address types. +// The output consists of a human-readable part (alphanumeric), a +// separator character (1), and a base32 data section, the last +// 6 characters of which are a checksum. +// +// For more information, see BIP 173. + +#include +#include +#include + +namespace sidechain { namespace bech32 { + +/** Encode a Bech32 string. Returns the empty string in case of failure. */ +std::string Encode(const std::string& hrp, const std::vector& values); + +/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */ +std::pair> Decode(const std::string& str); + +} } // namespace sidechain::bech32 diff --git a/libraries/sidechain/include/sidechain/bitcoin_address.hpp b/libraries/sidechain/include/sidechain/bitcoin_address.hpp new file mode 100644 index 000000000..16041e544 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_address.hpp @@ -0,0 +1,133 @@ +#pragma once + +#include +#include + +using namespace graphene::chain; + +namespace sidechain { + +const bytes op_num = {0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f}; // OP_1 - OP_15 + +class bitcoin_address +{ + +public: + + bitcoin_address() = default; + + bitcoin_address( const std::string& addr ) : address( addr ), type( determine_type() ), + raw_address( determine_raw_address() ) {} + + bool operator==( const bitcoin_address& btc_addr ) const; + + payment_type get_type() const { return type; } + + std::string get_address() const { return address; } + + bytes get_raw_address() const { return raw_address; } + + bytes get_script() const; + +private: + + enum size_segwit_address { P2WSH = 32, P2WPKH = 20 }; + + payment_type determine_type(); + + bytes determine_raw_address(); + + bool check_segwit_address( const size_segwit_address& size ) const; + + bool is_p2pk() const; + + bool is_p2wpkh() const; + + bool is_p2wsh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + +public: + + std::string address; + + payment_type type; + + bytes raw_address; + +}; + +class btc_multisig_address : public bitcoin_address +{ + +public: + + btc_multisig_address() = default; + + btc_multisig_address( const size_t n_required, const accounts_keys& keys ); + + size_t count_intersection( const accounts_keys& keys ) const; + + bytes get_redeem_script() const { return redeem_script; } + +private: + + void create_redeem_script(); + + void create_address(); + +public: + + enum address_types { MAINNET_SCRIPT = 5, TESTNET_SCRIPT = 196 }; + + enum { OP_0 = 0x00, OP_EQUAL = 0x87, OP_HASH160 = 0xa9, OP_CHECKMULTISIG = 0xae }; + + bytes redeem_script; + + size_t keys_required = 0; + + accounts_keys witnesses_keys; + +}; + +// multisig segwit address (P2WSH) +// https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa +class btc_multisig_segwit_address : public btc_multisig_address +{ + +public: + + btc_multisig_segwit_address() = default; + + btc_multisig_segwit_address( const size_t n_required, const accounts_keys& keys ); + + bool operator==( const btc_multisig_segwit_address& addr ) const; + + bytes get_witness_script() const { return witness_script; } + + std::vector get_keys(); + +private: + + void create_witness_script(); + + void create_segwit_address(); + + bytes get_address_bytes( const bytes& script_hash ); + +public: + + bytes witness_script; + +}; + +} + +FC_REFLECT( sidechain::bitcoin_address, (address)(type)(raw_address) ); + +FC_REFLECT_DERIVED( sidechain::btc_multisig_address, (sidechain::bitcoin_address), + (redeem_script)(keys_required)(witnesses_keys) ); + +FC_REFLECT_DERIVED( sidechain::btc_multisig_segwit_address, (sidechain::btc_multisig_address), (witness_script) ); diff --git a/libraries/sidechain/include/sidechain/bitcoin_script.hpp b/libraries/sidechain/include/sidechain/bitcoin_script.hpp new file mode 100644 index 000000000..1bd8e0617 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_script.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +namespace sidechain { + +enum class op { + // push value + _0 = 0x00, + _1 = 0x51, + _2 = 0x52, + _3 = 0x53, + _4 = 0x54, + _5 = 0x55, + _6 = 0x56, + _7 = 0x57, + _8 = 0x58, + _9 = 0x59, + _10 = 0x5a, + _11 = 0x5b, + _12 = 0x5c, + _13 = 0x5d, + _14 = 0x5e, + _15 = 0x5f, + _16 = 0x60, + + // control + RETURN = 0x6a, + + // stack ops + DUP = 0x76, + + // bit logic + EQUAL = 0x87, + EQUALVERIFY = 0x88, + + // crypto + HASH160 = 0xa9, + CHECKSIG = 0xac, + CHECKMULTISIG = 0xae, +}; + +class script_builder { + +public: + + script_builder& operator<<( op opcode ); + + script_builder& operator<<( uint8_t number ); + + script_builder& operator<<( size_t size ); + + script_builder& operator<<( const bytes& sc ); + + script_builder& operator<<( const fc::sha256& hash ); + + script_builder& operator<<( const fc::ripemd160& hash ); + + script_builder& operator<<( const fc::ecc::public_key_data& pubkey_data ); + + operator bytes() const { return std::move( script ); } + +private: + + bytes script; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp b/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp new file mode 100644 index 000000000..6271398a4 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp @@ -0,0 +1,100 @@ +#pragma once +#include + +namespace sidechain { + +struct out_point { + fc::sha256 hash; + uint32_t n = 0; + out_point() = default; + out_point( fc::sha256 _hash, uint32_t _n ) : hash( _hash ), n( _n ) {} + bool operator==( const out_point& op ) const; +}; + +struct tx_in { + + bool operator==( const tx_in& ti ) const; + + out_point prevout; + bytes scriptSig; + uint32_t nSequence = 0xffffffff; + std::vector scriptWitness; +}; + +struct tx_out { + int64_t value = 0; + bytes scriptPubKey; + + bool operator==( const tx_out& to ) const; + + bool is_p2wsh() const; + + bool is_p2wpkh() const; + + bool is_p2pkh() const; + + bool is_p2sh() const; + + bool is_p2pk() const; + + bytes get_data_or_script() const; +}; + +struct bitcoin_transaction { + + bool operator!=( const bitcoin_transaction& bt ) const; + + int32_t nVersion = 1; + std::vector vin; + std::vector vout; + uint32_t nLockTime = 0; + + fc::sha256 get_hash() const; + fc::sha256 get_txid() const; + size_t get_vsize() const; +}; + +class bitcoin_transaction_builder +{ + +public: + + bitcoin_transaction_builder() = default; + + bitcoin_transaction_builder( const bitcoin_transaction _tx ) : tx( _tx ) {} + + void set_version( int32_t version ); + + void set_locktime( uint32_t lock_time ); + + void add_in( payment_type type, const fc::sha256& txid, uint32_t n_out, + const bytes& script_code, bool front = false, uint32_t sequence = 0xffffffff ); + + void add_in( payment_type type, tx_in txin, const bytes& script_code, bool front = false ); + + void add_out( payment_type type, int64_t amount, const std::string& base58_address, bool front = false ); + + void add_out( payment_type type, int64_t amount, const fc::ecc::public_key_data& pubkey, bool front = false ); + + void add_out( payment_type type, int64_t amount, const bytes& script_code, bool front = false ); + + void add_out_all_type( const uint64_t& amount, const bitcoin_address& address, bool front = false ); + + bitcoin_transaction get_transaction() const { return tx; } + +private: + + inline bool is_payment_to_pubkey( payment_type type ); + + bytes get_script_pubkey( payment_type type, const bytes& script_code ); + + bitcoin_transaction tx; + +}; + +} + +FC_REFLECT( sidechain::out_point, (hash)(n) ) +FC_REFLECT( sidechain::tx_in, (prevout)(scriptSig)(nSequence)(scriptWitness) ) +FC_REFLECT( sidechain::tx_out, (value)(scriptPubKey) ) +FC_REFLECT( sidechain::bitcoin_transaction, (nVersion)(vin)(vout)(nLockTime) ) diff --git a/libraries/sidechain/include/sidechain/bitcoin_transaction_confirmations.hpp b/libraries/sidechain/include/sidechain/bitcoin_transaction_confirmations.hpp new file mode 100644 index 000000000..5b32aad2e --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_transaction_confirmations.hpp @@ -0,0 +1,61 @@ +#pragma once + +#include +#include +#include + +#include +#include + +#include + +using boost::multi_index_container; +using namespace boost::multi_index; + +namespace graphene { namespace chain { class database; } } + +namespace sidechain { + +struct bitcoin_transaction_confirmations +{ + bitcoin_transaction_confirmations() = default; + + bitcoin_transaction_confirmations( const fc::sha256& trx_id, const std::set& vins ) : + id( count_id_tx_conf++ ), transaction_id( trx_id ), valid_vins( vins ) {} + + struct comparer { + bool operator()( const bitcoin_transaction_confirmations& lhs, const bitcoin_transaction_confirmations& rhs ) const { + if( lhs.is_confirmed_and_not_used() != rhs.is_confirmed_and_not_used() ) + return lhs.is_confirmed_and_not_used() < rhs.is_confirmed_and_not_used(); + return lhs.id < rhs.id; + } + }; + + static uint64_t count_id_tx_conf; + uint64_t id; + + bool is_confirmed_and_not_used() const { return !used && confirmed; } + bool is_missing_and_not_used() const { return !used && missing; } + + fc::sha256 transaction_id; + std::set valid_vins; + + uint64_t count_block = 0; + bool confirmed = false; + bool missing = false; + bool used = false; +}; + +struct by_hash; +struct by_confirmed_and_not_used; +struct by_missing_and_not_used; + +using btc_tx_confirmations_index = boost::multi_index_container, member>, + ordered_non_unique, identity< bitcoin_transaction_confirmations >, bitcoin_transaction_confirmations::comparer >, + ordered_non_unique, identity< bitcoin_transaction_confirmations >, bitcoin_transaction_confirmations::comparer > + > +>; + +} \ No newline at end of file diff --git a/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp b/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp new file mode 100644 index 000000000..4210002b0 --- /dev/null +++ b/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp @@ -0,0 +1,114 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include + +#include +#include + +using boost::multi_index_container; +using namespace boost::multi_index; + +namespace graphene { namespace chain { class database; } } + +namespace sidechain { + +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 by_id; +struct by_identifier; +struct by_id_resend_not_used; + +using info_for_vin_index = boost::multi_index_container, member>, + ordered_unique, member>, + ordered_non_unique, identity< info_for_vin >, info_for_vin::comparer > + > +>; + +class input_withdrawal_info +{ + +public: + input_withdrawal_info( graphene::chain::database& _db ) : db( _db ) {} + + + std::vector get_redeem_scripts( const std::vector& info_vins ); + + std::vector get_amounts( const std::vector& info_vins ); + + + fc::optional get_info_for_pw_vin(); + + + void insert_info_for_vin( const prev_out& out, const std::string& address, bytes script = bytes(), bool resend = false ); + + void modify_info_for_vin( const info_for_vin& obj, const std::function& func ); + + void mark_as_used_vin( const info_for_vin& obj ); + + void mark_as_unused_vin( const info_for_vin& obj ); + + void remove_info_for_vin( const info_for_vin& obj ); + + fc::optional find_info_for_vin( fc::sha256 identifier ); + + size_t size_info_for_vins() { return info_for_vins.size(); } + + std::vector get_info_for_vins(); + + + void insert_info_for_vout( const graphene::chain::account_id_type& payer, const std::string& data, const uint64_t& amount ); + + void mark_as_used_vout( const graphene::chain::info_for_vout_object& obj ); + + void mark_as_unused_vout( const graphene::chain::info_for_vout_object& obj ); + + void remove_info_for_vout( const info_for_vout& obj ); + + fc::optional find_info_for_vout( graphene::chain::info_for_vout_id_type id ); + + size_t size_info_for_vouts(); + + std::vector get_info_for_vouts(); + +private: + + graphene::chain::database& db; + + thread_safe_index info_for_vins; + +}; + +} + +FC_REFLECT( sidechain::info_for_vin, (identifier)(out)(address)(script)(used)(resend) ) \ No newline at end of file diff --git a/libraries/sidechain/include/sidechain/primary_wallet_vout_manager.hpp b/libraries/sidechain/include/sidechain/primary_wallet_vout_manager.hpp new file mode 100644 index 000000000..874c4c881 --- /dev/null +++ b/libraries/sidechain/include/sidechain/primary_wallet_vout_manager.hpp @@ -0,0 +1,46 @@ +#pragma once + +#include +#include +#include + +namespace graphene { namespace chain { class database; } } + +using graphene::chain::primary_wallet_vout_object; +using graphene::chain::primary_wallet_vout_id_type; + +namespace sidechain { + +class primary_wallet_vout_manager +{ + +public: + primary_wallet_vout_manager( graphene::chain::database& _db ) : db( _db ) {} + + bool is_max_vouts() const; + + fc::optional< primary_wallet_vout_object > get_latest_unused_vout() const; + + fc::optional< primary_wallet_vout_object > get_vout( fc::sha256 hash_id ) const; + + void create_new_vout( const sidechain::prev_out& out ); + + void delete_vouts_after( fc::sha256 hash_id ); + + void confirm_vout( fc::sha256 hash_id ); + + void mark_as_used_vout( fc::sha256 hash_id ); + + void mark_as_unused_vout( fc::sha256 hash_id ); + +private: + + fc::sha256 create_hash_id( const std::string& hash_tx, const uint32_t& n_vout, const uint64_t& id ) const; + + fc::optional< primary_wallet_vout_id_type > get_vout_id( fc::sha256 hash_id ) const; + + graphene::chain::database& db; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/segwit_addr.hpp b/libraries/sidechain/include/sidechain/segwit_addr.hpp new file mode 100644 index 000000000..d115e035e --- /dev/null +++ b/libraries/sidechain/include/sidechain/segwit_addr.hpp @@ -0,0 +1,34 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include +#include + +namespace sidechain { namespace segwit_addr { + +/** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */ +std::pair > decode(const std::string& hrp, const std::string& addr); + +/** Encode a SegWit address. Empty string means failure. */ +std::string encode(const std::string& hrp, int witver, const std::vector& witprog); + +} } diff --git a/libraries/sidechain/include/sidechain/serialize.hpp b/libraries/sidechain/include/sidechain/serialize.hpp new file mode 100644 index 000000000..12b6e7fb8 --- /dev/null +++ b/libraries/sidechain/include/sidechain/serialize.hpp @@ -0,0 +1,360 @@ +#pragma once + +#include +#include + +namespace sidechain { + +inline void write_compact_size( bytes& vec, size_t size ) +{ + bytes sb; + sb.reserve( 2 ); + if ( size < 253 ) { + sb.insert( sb.end(), static_cast( size ) ); + } else if ( size <= std::numeric_limits::max() ) { + uint16_t tmp = htole16( static_cast( size ) ); + sb.insert( sb.end(), static_cast( 253 ) ); + sb.insert( sb.end(), reinterpret_cast( tmp ), reinterpret_cast( tmp ) + sizeof( tmp ) ); + } else if ( size <= std::numeric_limits::max() ) { + uint32_t tmp = htole32( static_cast( size ) ); + sb.insert( sb.end(), static_cast( 254 ) ); + sb.insert( sb.end(), reinterpret_cast( tmp ), reinterpret_cast( tmp ) + sizeof( tmp ) ); + } else { + uint64_t tmp = htole64( static_cast( size ) ); + sb.insert( sb.end(), static_cast( 255 ) ); + sb.insert( sb.end(), reinterpret_cast( tmp ), reinterpret_cast( tmp ) + sizeof( tmp ) ); + } + vec.insert( vec.end(), sb.begin(), sb.end() ); +} + +template +inline void pack_compact_size(Stream& s, size_t size) +{ + if ( size < 253 ) { + fc::raw::pack( s, static_cast( size ) ); + } else if ( size <= std::numeric_limits::max() ) { + fc::raw::pack( s, static_cast( 253 ) ); + fc::raw::pack( s, htole16( static_cast( size ) ) ); + } else if ( size <= std::numeric_limits::max() ) { + fc::raw::pack( s, static_cast( 254 ) ); + fc::raw::pack( s, htole32(static_cast( size ) ) ); + } else { + fc::raw::pack( s, static_cast( 255 ) ); + fc::raw::pack( s, htole64( static_cast( size ) ) ); + } +} + +template +inline uint64_t unpack_compact_size( Stream& s ) +{ + uint8_t size; + uint64_t size_ret; + + fc::raw::unpack( s, size ); + + if (size < 253) { + size_ret = size; + } else if ( size == 253 ) { + uint16_t tmp; + fc::raw::unpack( s, tmp ); + size_ret = le16toh( tmp ); + if ( size_ret < 253 ) + FC_THROW_EXCEPTION( fc::parse_error_exception, "non-canonical unpack_compact_size()" ); + } else if ( size == 254 ) { + uint32_t tmp; + fc::raw::unpack( s, tmp ); + size_ret = le32toh( tmp ); + if ( size_ret < 0x10000u ) + FC_THROW_EXCEPTION( fc::parse_error_exception, "non-canonical unpack_compact_size()" ); + } else { + uint32_t tmp; + fc::raw::unpack( s, tmp ); + size_ret = le64toh( tmp ); + if ( size_ret < 0x100000000ULL ) + FC_THROW_EXCEPTION( fc::parse_error_exception, "non-canonical unpack_compact_size()" ); + } + + if ( size_ret > 0x08000000 ) + FC_THROW_EXCEPTION( fc::parse_error_exception, "unpack_compact_size(): size too large" ); + + return size_ret; +} + +template +inline void pack( Stream& s, const std::vector& v ) +{ + pack_compact_size( s, v.size() ); + if ( !v.empty() ) + s.write( v.data(), v.size() ); +} + +template +inline void unpack( Stream& s, std::vector& v ) +{ + const auto size = unpack_compact_size( s ); + v.resize( size ); + if ( size ) + s.read( v.data(), size ); +} + +template +inline void pack( Stream& s, const T& val ) +{ + fc::raw::pack( s, val ); +} + +template +inline void unpack( Stream& s, T& val ) +{ + fc::raw::unpack( s, val ); +} + +template +inline void pack( Stream& s, const out_point& op ) +{ + fc::sha256 reversed( op.hash ); + std::reverse( reversed.data(), reversed.data() + reversed.data_size() ); + s.write( reversed.data(), reversed.data_size() ); + pack( s, op.n ); +} + +template +inline void unpack( Stream& s, out_point& op ) +{ + uint64_t hash_size = op.hash.data_size(); + std::unique_ptr hash_data( new char[hash_size] ); + s.read( hash_data.get(), hash_size ); + std::reverse( hash_data.get(), hash_data.get() + hash_size ); + + op.hash = fc::sha256( hash_data.get(), hash_size ); + unpack( s, op.n ); +} + +template +inline void pack( Stream& s, const tx_in& in ) +{ + pack( s, in.prevout ); + pack( s, in.scriptSig ); + pack( s, in.nSequence ); +} + +template +inline void unpack( Stream& s, tx_in& in ) +{ + unpack( s, in.prevout ); + unpack( s, in.scriptSig ); + unpack( s, in.nSequence ); +} + +template +inline void pack( Stream& s, const tx_out& out ) +{ + pack( s, out.value ); + pack( s, out.scriptPubKey ); +} + +template +inline void unpack( Stream& s, tx_out& out ) +{ + unpack( s, out.value ); + unpack( s, out.scriptPubKey ); +} + +template +inline void pack( Stream& s, const bitcoin_transaction& tx, bool with_witness = true ) +{ + uint8_t flags = 0; + + if ( with_witness ) { + for ( const auto& in : tx.vin ) { + if ( !in.scriptWitness.empty() ) { + flags |= 1; + break; + } + } + } + + pack( s, tx.nVersion ); + + if ( flags ) { + pack_compact_size( s, 0 ); + pack( s, flags ); + } + + pack_compact_size( s, tx.vin.size() ); + for ( const auto& in : tx.vin ) + pack( s, in ); + + pack_compact_size( s, tx.vout.size() ); + for ( const auto& out : tx.vout ) + pack( s, out ); + + if ( flags & 1 ) { + for ( const auto in : tx.vin ) { + pack_compact_size( s, in.scriptWitness.size() ); + for ( const auto& sc : in.scriptWitness ) + pack( s, sc ); + } + } + + pack( s, tx.nLockTime ); +} + +template +inline void unpack( Stream& s, bitcoin_transaction& tx ) +{ + uint8_t flags = 0; + + unpack( s, tx.nVersion ); + + auto vin_size = unpack_compact_size( s ); + if ( vin_size == 0 ) { + unpack( s, flags ); + vin_size = unpack_compact_size( s ); + } + + tx.vin.reserve( vin_size ); + for ( size_t i = 0; i < vin_size; i++ ) { + tx_in in; + unpack( s, in ); + tx.vin.push_back( in ); + } + + const auto vout_size = unpack_compact_size( s ); + tx.vout.reserve( vout_size ); + for ( size_t i = 0; i < vout_size; i++ ) { + tx_out out; + unpack( s, out ); + tx.vout.push_back( out ); + } + + if ( flags & 1 ) { + for ( auto& in : tx.vin ) { + uint64_t stack_size = unpack_compact_size( s ); + in.scriptWitness.reserve( stack_size ); + for ( uint64_t i = 0; i < stack_size; i++ ) { + std::vector script; + unpack( s, script ); + in.scriptWitness.push_back( script ); + } + } + } + + unpack( s, tx.nLockTime ); +} + +inline std::vector pack( const bitcoin_transaction& v, bool with_witness = true ) +{ + fc::datastream ps; + pack( ps, v, with_witness ); + std::vector vec( ps.tellp() ); + + if( !vec.empty() ) { + fc::datastream ds( vec.data(), size_t( vec.size() ) ); + pack( ds, v, with_witness ); + } + return vec; +} + +inline bitcoin_transaction unpack( const std::vector& s ) +{ try { + bitcoin_transaction tmp; + if( !s.empty() ) { + fc::datastream ds( s.data(), size_t( s.size() ) ); + unpack(ds, tmp); + } + return tmp; +} FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type","transaction" ) ) } + +template +inline void pack_tx_signature( Stream& s, const std::vector& scriptPubKey, const bitcoin_transaction& tx, unsigned int in_index, int hash_type ) +{ + pack( s, tx.nVersion ); + + pack_compact_size( s, tx.vin.size() ); + for ( size_t i = 0; i < tx.vin.size(); i++ ) { + const auto& in = tx.vin[i]; + pack( s, in.prevout ); + if ( i == in_index ) + pack( s, scriptPubKey ); + else + pack_compact_size( s, 0 ); // Blank signature + pack( s, in.nSequence ); + } + + pack_compact_size( s, tx.vout.size() ); + for ( const auto& out : tx.vout ) + pack( s, out ); + + pack( s, tx.nLockTime ); + pack( s, hash_type ); +} + +template +inline void pack_tx_witness_signature( Stream& s, const std::vector& scriptCode, const bitcoin_transaction& tx, unsigned int in_index, int64_t amount, int hash_type ) +{ + + fc::sha256 hash_prevouts; + fc::sha256 hash_sequence; + fc::sha256 hash_output; + + { + fc::datastream ps; + for ( const auto in : tx.vin ) + pack( ps, in.prevout ); + + std::vector vec( ps.tellp() ); + if ( vec.size() ) { + fc::datastream ds( vec.data(), size_t( vec.size() ) ); + for ( const auto in : tx.vin ) + pack( ds, in.prevout ); + } + + hash_prevouts = fc::sha256::hash( fc::sha256::hash( vec.data(), vec.size() ) ); + } + + { + fc::datastream ps; + for ( const auto in : tx.vin ) + pack( ps, in.nSequence ); + + std::vector vec( ps.tellp() ); + if ( vec.size() ) { + fc::datastream ds( vec.data(), size_t( vec.size() ) ); + for ( const auto in : tx.vin ) + pack( ds, in.nSequence ); + } + + hash_sequence = fc::sha256::hash( fc::sha256::hash( vec.data(), vec.size() ) ); + }; + + { + fc::datastream ps; + for ( const auto out : tx.vout ) + pack( ps, out ); + + std::vector vec( ps.tellp() ); + if ( vec.size() ) { + fc::datastream ds( vec.data(), size_t( vec.size() ) ); + for ( const auto out : tx.vout ) + pack( ds, out ); + } + + hash_output = fc::sha256::hash( fc::sha256::hash( vec.data(), vec.size() ) ); + } + + pack( s, tx.nVersion ); + pack( s, hash_prevouts ); + pack( s, hash_sequence ); + + pack( s, tx.vin[in_index].prevout ); + pack( s, scriptCode ); + pack( s, amount ); + pack( s, tx.vin[in_index].nSequence ); + + pack( s, hash_output ); + pack( s, tx.nLockTime ); + pack( s, hash_type ); +} + +} diff --git a/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp b/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp new file mode 100644 index 000000000..5ce0fac25 --- /dev/null +++ b/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include + +using namespace sidechain; + +namespace sidechain { + +class sidechain_condensing_tx +{ + +public: + + sidechain_condensing_tx( const std::vector& vin_info, const std::vector& vout_info ); + + void create_pw_vin( const info_for_vin& vin_info, bool front = true ); + + void create_pw_vout( const uint64_t amount, const bytes& wit_script_out, bool front = true ); + + void create_vouts_for_witness_fee( const accounts_keys& witness_active_keys, bool front = true ); + + static uint64_t get_estimate_tx_size( bitcoin_transaction tx, size_t number_witness ); + + void subtract_fee( const uint64_t& fee, const uint16_t& witnesses_percentage ); + + bitcoin_transaction get_transaction() const { return tb.get_transaction(); } + + uint64_t get_amount_vins() { return amount_vins; } + + uint64_t get_amount_transfer_to_bitcoin() { return amount_transfer_to_bitcoin; } + +private: + + void create_vins_for_condensing_tx( const std::vector& vin_info ); + + void create_vouts_for_condensing_tx( const std::vector& vout_info ); + + uint32_t count_transfer_vin = 0; + + uint32_t count_transfer_vout = 0; + + uint32_t count_witness_vout = 0; + + uint64_t amount_vins = 0; + + uint64_t amount_transfer_to_bitcoin = 0; + + bitcoin_transaction_builder tb; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/sidechain_parameters.hpp b/libraries/sidechain/include/sidechain/sidechain_parameters.hpp new file mode 100644 index 000000000..1aedc117c --- /dev/null +++ b/libraries/sidechain/include/sidechain/sidechain_parameters.hpp @@ -0,0 +1,30 @@ +#pragma once +#include + + +namespace sidechain { + + struct sidechain_parameters_extension { + uint8_t maximum_condensing_tx_vins = SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VINS; + uint8_t maximum_condensing_tx_vouts = SIDECHAIN_DEFAULT_MAX_CONDENSING_TX_VOUTS; + uint8_t maximum_unconfirmed_vouts = SIDECHAIN_DEFAULT_MAX_UNCONFIRMED_VOUTS; + uint16_t percent_payment_to_witnesses = SIDECHAIN_DEFAULT_PERCENT_PAYMENT_TO_WITNESSES; + uint8_t multisig_sigs_num = SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG; + uint8_t confirmations_num = SIDECHAIN_DEFAULT_NUMBER_OF_CONFIRMATIONS; + + graphene::chain::account_id_type managing_account; + graphene::chain::asset_id_type asset_id; + }; + +} + +FC_REFLECT( sidechain::sidechain_parameters_extension, + (maximum_condensing_tx_vins) + (maximum_condensing_tx_vouts) + (maximum_unconfirmed_vouts) + (percent_payment_to_witnesses) + (multisig_sigs_num) + (confirmations_num) + (managing_account) + (asset_id) +) \ No newline at end of file diff --git a/libraries/sidechain/include/sidechain/sidechain_proposal_checker.hpp b/libraries/sidechain/include/sidechain/sidechain_proposal_checker.hpp new file mode 100644 index 000000000..fce1c4258 --- /dev/null +++ b/libraries/sidechain/include/sidechain/sidechain_proposal_checker.hpp @@ -0,0 +1,52 @@ +#pragma once +#include +#include +#include +#include + +namespace graphene { namespace chain { class database; } }; + +namespace sidechain { + +class sidechain_proposal_checker +{ + +public: + + sidechain_proposal_checker( graphene::chain::database& _db ) : db(_db) {} + + bool check_bitcoin_transaction_send_operation( const bitcoin_transaction_send_operation& op ); + + bool check_reuse( const graphene::chain::operation& op ); + + bool check_witness_opportunity_to_approve( const witness_object& current_witness, const proposal_object& proposal ); + + bool check_witnesses_keys( const witness_object& current_witness, const proposal_object& proposal ); + + bool check_bitcoin_transaction_revert_operation( const bitcoin_transaction_revert_operation& op ); + +private: + + bool check_info_for_pw_vin( const info_for_vin& info_for_vin ); + + bool check_info_for_vins( const std::vector& info_for_vins ); + + bool check_info_for_vouts( const std::vector& info_for_vout_ids ); + + bool check_transaction( const bitcoin_transaction_send_operation& btc_trx_op ); + + bool check_reuse_pw_vin( const fc::sha256& pw_vin ); + + bool check_reuse_user_vins( const std::vector& user_vin_identifiers ); + + bool check_reuse_vouts( const std::vector& user_vout_ids ); + + std::set pw_vin_ident; + std::set user_vin_ident; + std::set vout_ids; + + graphene::chain::database& db; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/sign_bitcoin_transaction.hpp b/libraries/sidechain/include/sidechain/sign_bitcoin_transaction.hpp new file mode 100644 index 000000000..6c2ade490 --- /dev/null +++ b/libraries/sidechain/include/sidechain/sign_bitcoin_transaction.hpp @@ -0,0 +1,27 @@ +#pragma once + +#include +#include +#include + +namespace sidechain { + +class bitcoin_transaction; + +fc::sha256 get_signature_hash( const bitcoin_transaction& tx, const bytes& scriptPubKey, int64_t amount, + size_t in_index, int hash_type, bool is_witness ); + +std::vector privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign = nullptr ); + +std::vector sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const bytes& privkey, + const secp256k1_context_t* context_sign = nullptr, int hash_type = 1 ); + +void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector& redeem_scripts ); + +bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context ); + +std::vector> sort_sigs( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const secp256k1_context_t* context ); + +} diff --git a/libraries/sidechain/include/sidechain/thread_safe_index.hpp b/libraries/sidechain/include/sidechain/thread_safe_index.hpp new file mode 100644 index 000000000..1742695b4 --- /dev/null +++ b/libraries/sidechain/include/sidechain/thread_safe_index.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +namespace sidechain { + +template +class thread_safe_index +{ + +public: + + using iterator = typename T1::iterator; + + std::pair insert( const typename T1::value_type& value ) { + std::lock_guard locker( lock ); + return data.insert( value ); + } + + template + bool modify( const Key _key, const std::function& func ) { + std::lock_guard locker( lock ); + const auto option = find_iterator( _key ); + if( !option ) + return false; + data.modify( data.iterator_to( **option ), [&func]( typename T1::value_type& obj ) { func( obj ); } ); + return true; + } + + template + bool remove( const Key _key ) { + std::lock_guard locker( lock ); + const auto option = find_iterator( _key ); + if( !option ) + return false; + data.erase( data.iterator_to( **option ) ); + return true; + } + + size_t size() { + std::lock_guard locker( lock ); + return data.size(); + } + + template + fc::optional find( const Key _key ) { + std::lock_guard locker( lock ); + auto& index = data.template get(); + auto it = index.find( _key ); + if( it != index.end() ) { + return fc::optional(*it); + } + return fc::optional(); + } + + template + void safe_for(std::function::type::iterator itr1, + typename T1::template index::type::iterator itr2)> func) { + std::lock_guard locker( lock ); + auto& index = data.template get(); + func(index.begin(), index.end()); + } + +private: + + template + fc::optional::type::iterator> find_iterator( const Key _key ) { + auto& index = data.template get(); + auto it = index.find( _key ); + if( it == index.end() ) + return fc::optional::type::iterator>(); + return it; + } + + std::recursive_mutex lock; + + T1 data; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/types.hpp b/libraries/sidechain/include/sidechain/types.hpp new file mode 100644 index 000000000..bbe3ec4c1 --- /dev/null +++ b/libraries/sidechain/include/sidechain/types.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace sidechain { + +class bitcoin_transaction; + +using bytes = std::vector; +using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >; +using info_for_vout = graphene::chain::info_for_vout_object; +using full_btc_transaction = std::pair; + +enum class payment_type +{ + NULLDATA, + P2PK, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2SH_WPKH, + P2SH_WSH +}; + +enum class sidechain_proposal_type +{ + ISSUE_BTC, + SEND_BTC_TRANSACTION, + REVERT_BTC_TRANSACTION +}; + +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; +}; + +} + +FC_REFLECT_ENUM( sidechain::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH) ); +FC_REFLECT_ENUM( sidechain::sidechain_proposal_type, (ISSUE_BTC)(SEND_BTC_TRANSACTION)(REVERT_BTC_TRANSACTION) ); +FC_REFLECT( sidechain::prev_out, (hash_tx)(n_vout)(amount) ); diff --git a/libraries/sidechain/include/sidechain/utils.hpp b/libraries/sidechain/include/sidechain/utils.hpp new file mode 100644 index 000000000..276f888f6 --- /dev/null +++ b/libraries/sidechain/include/sidechain/utils.hpp @@ -0,0 +1,15 @@ +#pragma once + +#include +#include +#include + +namespace sidechain { + +bytes parse_hex( const std::string& str ); + +std::vector get_pubkey_from_redeemScript( bytes script ); + +bytes public_key_data_to_bytes( const fc::ecc::public_key_data& key ); + +} diff --git a/libraries/sidechain/input_withdrawal_info.cpp b/libraries/sidechain/input_withdrawal_info.cpp new file mode 100644 index 000000000..4711f0273 --- /dev/null +++ b/libraries/sidechain/input_withdrawal_info.cpp @@ -0,0 +1,207 @@ +#include +#include +#include +#include +#include + +namespace sidechain { + +uint64_t info_for_vin::count_id_info_for_vin = 0; +uint64_t bitcoin_transaction_confirmations::count_id_tx_conf = 0; + +info_for_vin::info_for_vin( const prev_out& _out, const std::string& _address, bytes _script, bool _resend ) : + id( count_id_info_for_vin++ ), out( _out ), address( _address ), script( _script ), resend( _resend ) +{ + identifier = fc::sha256::hash( out.hash_tx + std::to_string( out.n_vout ) ); +} + +bool info_for_vin::operator!=( const info_for_vin& obj ) const +{ + if( this->identifier != obj.identifier || + this->out.hash_tx != obj.out.hash_tx || + this->out.n_vout != obj.out.n_vout || + this->out.amount != obj.out.amount || + this->address != obj.address || + this->script != obj.script ) + { + return true; + } + return false; +} + +bool info_for_vin::comparer::operator() ( const info_for_vin& lhs, const info_for_vin& rhs ) const +{ + if( lhs.used != rhs.used ) { + return lhs.used < rhs.used; + } else if ( lhs.resend != rhs.resend ) { + return lhs.resend > rhs.resend; + } + return lhs.id < rhs.id; +} + +std::vector input_withdrawal_info::get_redeem_scripts( const std::vector& info_vins ) +{ + std::vector redeem_scripts; + const auto& bitcoin_address_idx = db.get_index_type().indices().get< by_address >(); + for( const auto& v : info_vins ) { + const auto& pbtc_address = bitcoin_address_idx.find( v.address ); + redeem_scripts.push_back( pbtc_address->address.get_redeem_script() ); + } + return redeem_scripts; +} + +std::vector input_withdrawal_info::get_amounts( const std::vector& info_vins ) +{ + std::vector amounts; + for( const auto& v : info_vins ) { + amounts.push_back( v.out.amount ); + } + return amounts; +} + +fc::optional input_withdrawal_info::get_info_for_pw_vin() +{ + fc::optional< primary_wallet_vout_object > vout = db.pw_vout_manager.get_latest_unused_vout(); + if( !vout.valid() || db.pw_vout_manager.is_max_vouts() ) { + return fc::optional(); + } + + const auto& pw_address = db.get_latest_PW().address; + + info_for_vin vin; + vin.identifier = vout->hash_id; + vin.out = vout->vout; + vin.address = pw_address.get_address(); + vin.script = pw_address.get_witness_script(); + + return vin; +} + +void input_withdrawal_info::insert_info_for_vin( const prev_out& out, const std::string& address, bytes script, bool resend ) +{ + info_for_vins.insert( info_for_vin( out, address, script, resend ) ); +} + +void input_withdrawal_info::modify_info_for_vin( const info_for_vin& obj, const std::function& func ) +{ + info_for_vins.modify( obj.identifier, func ); +} + +void input_withdrawal_info::mark_as_used_vin( const info_for_vin& obj ) +{ + info_for_vins.modify( obj.identifier, [&]( info_for_vin& o ) { + o.used = true; + }); +} + +void input_withdrawal_info::mark_as_unused_vin( const info_for_vin& obj ) +{ + info_for_vins.modify( obj.identifier, [&]( info_for_vin& o ) { + o.used = false; + }); +} + +void input_withdrawal_info::remove_info_for_vin( const info_for_vin& obj ) +{ + info_for_vins.remove( obj.identifier ); +} + +fc::optional input_withdrawal_info::find_info_for_vin( fc::sha256 identifier ) +{ + return info_for_vins.find( identifier ); +} + +std::vector input_withdrawal_info::get_info_for_vins() +{ + std::vector result; + const auto max_vins = db.get_sidechain_params().maximum_condensing_tx_vins; + + info_for_vins.safe_for( [&]( info_for_vin_index::index::type::iterator itr_b, + info_for_vin_index::index::type::iterator itr_e ) + { + for( size_t i = 0; itr_b != itr_e && i < max_vins && !itr_b->used; i++ ) { + info_for_vin vin; + vin.identifier = itr_b->identifier; + vin.out.hash_tx = itr_b->out.hash_tx; + vin.out.n_vout = itr_b->out.n_vout; + vin.out.amount = itr_b->out.amount; + vin.address = itr_b->address; + vin.script = itr_b->script; + + result.push_back( vin ); + ++itr_b; + } + } ); + + return result; +} + +void input_withdrawal_info::insert_info_for_vout( const graphene::chain::account_id_type& payer, const std::string& data, const uint64_t& amount ) +{ + db.create([&](graphene::chain::info_for_vout_object& obj) { + obj.payer = payer; + obj.address = bitcoin_address( data ); + obj.amount = amount; + }); +} + +void input_withdrawal_info::mark_as_used_vout( const graphene::chain::info_for_vout_object& obj ) +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto itr = info_for_vout_idx.find( obj.id ); + + db.modify( *itr, [&]( graphene::chain::info_for_vout_object& o ) { + o.used = true; + }); +} + +void input_withdrawal_info::mark_as_unused_vout( const graphene::chain::info_for_vout_object& obj ) +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto itr = info_for_vout_idx.find( obj.id ); + + db.modify( *itr, [&]( graphene::chain::info_for_vout_object& o ) { + o.used = false; + }); +} + +void input_withdrawal_info::remove_info_for_vout( const info_for_vout& obj ) +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto itr = info_for_vout_idx.find( obj.id ); + db.remove( *itr ); +} + +fc::optional input_withdrawal_info::find_info_for_vout( graphene::chain::info_for_vout_id_type id ) +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto itr = info_for_vout_idx.find( id ); + if( itr != info_for_vout_idx.end() ) + return fc::optional( *itr ); + return fc::optional(); +} + +size_t input_withdrawal_info::size_info_for_vouts() +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + return info_for_vout_idx.size(); +} + +std::vector input_withdrawal_info::get_info_for_vouts() +{ + std::vector result; + + const auto& info_for_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id_and_not_used >(); + const auto max_vouts = db.get_sidechain_params().maximum_condensing_tx_vouts; + + auto itr = info_for_vout_idx.begin(); + for(size_t i = 0; i < max_vouts && itr != info_for_vout_idx.end() && !itr->used; i++) { + + result.push_back( *itr ); + ++itr; + } + + return result; +} + +} diff --git a/libraries/sidechain/network/CMakeLists.txt b/libraries/sidechain/network/CMakeLists.txt new file mode 100644 index 000000000..c7e3ca913 --- /dev/null +++ b/libraries/sidechain/network/CMakeLists.txt @@ -0,0 +1,7 @@ +file( GLOB HEADERS "include/sidechain/network/*.hpp" ) +file( GLOB SOURCES "*.cpp" ) + +add_library( sidechain_network STATIC ${SOURCES} ${HEADERS} ) + +target_link_libraries( sidechain_network PUBLIC graphene_chain fc zmq ) +target_include_directories( sidechain_network PUBLIC "${CMAKE_CURRENT_SOURCE_DIR}/include" ) diff --git a/libraries/sidechain/network/bitcoin_rpc_client.cpp b/libraries/sidechain/network/bitcoin_rpc_client.cpp new file mode 100644 index 000000000..9a1609e37 --- /dev/null +++ b/libraries/sidechain/network/bitcoin_rpc_client.cpp @@ -0,0 +1,140 @@ +#include + +#include + +#include + +#include +#include + +namespace sidechain { + +bitcoin_rpc_client::bitcoin_rpc_client( std::string _ip, uint32_t _rpc, std::string _user, std::string _password ): + ip( _ip ), rpc_port( _rpc ), user( _user ), password( _password ) +{ + authorization.key = "Authorization"; + authorization.val = "Basic " + fc::base64_encode( user + ":" + password ); +} + +std::string bitcoin_rpc_client::receive_full_block( const std::string& block_hash ) +{ + fc::http::connection conn; + conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); + + const auto url = "http://" + ip + ":" + std::to_string( rpc_port ) + "/rest/block/" + block_hash + ".json"; + + const auto reply = conn.request( "GET", url ); + if ( reply.status != 200 ) + return ""; + + ilog( "Receive Bitcoin block: ${hash}", ( "hash", block_hash ) ); + return std::string( reply.body.begin(), reply.body.end() ); +} + +int32_t bitcoin_rpc_client::receive_confirmations_tx( const std::string& tx_hash ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", \"method\": \"getrawtransaction\", \"params\": [") + + std::string("\"") + tx_hash + std::string("\"") + ", " + "true" + std::string("] }"); + + const auto reply = send_post_request( body ); + + if ( reply.status != 200 ) + return 0; + + const auto result = std::string( reply.body.begin(), reply.body.end() ); + + std::stringstream ss( result ); + boost::property_tree::ptree tx; + boost::property_tree::read_json( ss, tx ); + + if( tx.count( "result" ) ) { + if( tx.get_child( "result" ).count( "confirmations" ) ) { + return tx.get_child( "result" ).get_child( "confirmations" ).get_value(); + } + } + return 0; + +} + +bool bitcoin_rpc_client::receive_mempool_entry_tx( const std::string& tx_hash ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"curltest\", \"method\": \"getmempoolentry\", \"params\": [") + + std::string("\"") + tx_hash + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request( body ); + + if ( reply.status != 200 ) + return false; + + return true; +} + +uint64_t bitcoin_rpc_client::receive_estimated_fee() +{ + static const auto confirmation_target_blocks = 6; + + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"estimated_feerate\", \"method\": \"estimatesmartfee\", \"params\": [") + + std::to_string(confirmation_target_blocks) + std::string("] }"); + + const auto reply = send_post_request( body ); + + if( reply.status != 200 ) + 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( json.count( "result" ) ) + if ( json.get_child( "result" ).count( "feerate" ) ) { + auto feerate_str = json.get_child( "result" ).get_child( "feerate" ).get_value(); + feerate_str.erase( std::remove( feerate_str.begin(), feerate_str.end(), '.' ), feerate_str.end() ); + return std::stoll( feerate_str ); + } + return 0; +} + +void bitcoin_rpc_client::send_btc_tx( const std::string& tx_hex ) +{ + const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"send_tx\", \"method\": \"sendrawtransaction\", \"params\": [") + + std::string("\"") + tx_hex + std::string("\"") + std::string("] }"); + + const auto reply = send_post_request( body ); + + if( reply.body.empty() ) + return; + + std::string reply_str( reply.body.begin(), reply.body.end() ); + + std::stringstream ss(reply_str); + boost::property_tree::ptree json; + boost::property_tree::read_json( ss, json ); + + if( reply.status == 200 ) { + idump(( tx_hex )); + return; + } 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; + + wlog( "BTC tx is not sent! Reply: ${msg}", ("msg", reply_str) ); + } +} + +bool bitcoin_rpc_client::connection_is_not_defined() const +{ + return ip.empty() || rpc_port == 0 || user.empty() || password.empty(); +} + +fc::http::reply bitcoin_rpc_client::send_post_request( std::string body ) +{ + fc::http::connection conn; + conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) ); + + const auto url = "http://" + ip + ":" + std::to_string( rpc_port ); + + return conn.request( "POST", url, body, fc::http::headers{authorization} ); +} + +} \ No newline at end of file diff --git a/libraries/sidechain/network/include/sidechain/network/bitcoin_rpc_client.hpp b/libraries/sidechain/network/include/sidechain/network/bitcoin_rpc_client.hpp new file mode 100644 index 000000000..f94d789db --- /dev/null +++ b/libraries/sidechain/network/include/sidechain/network/bitcoin_rpc_client.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include +#include + +namespace sidechain { + +class bitcoin_rpc_client +{ + +public: + + bitcoin_rpc_client( std::string _ip, uint32_t _rpc, std::string _user, std::string _password) ; + + std::string receive_full_block( const std::string& block_hash ); + + int32_t receive_confirmations_tx( const std::string& tx_hash ); + + bool receive_mempool_entry_tx( const std::string& tx_hash ); + + uint64_t receive_estimated_fee(); + + void send_btc_tx( const std::string& tx_hex ); + + bool connection_is_not_defined() const; + +private: + + fc::http::reply send_post_request( std::string body ); + + std::string ip; + uint32_t rpc_port; + std::string user; + std::string password; + + fc::http::header authorization; + +}; + +} diff --git a/libraries/sidechain/network/include/sidechain/network/sidechain_net_manager.hpp b/libraries/sidechain/network/include/sidechain/network/sidechain_net_manager.hpp new file mode 100644 index 000000000..16c524c7f --- /dev/null +++ b/libraries/sidechain/network/include/sidechain/network/sidechain_net_manager.hpp @@ -0,0 +1,52 @@ +#pragma once + +#include +#include +#include + +namespace sidechain { + +class sidechain_net_manager +{ + +public: + + sidechain_net_manager() {}; + + sidechain_net_manager( graphene::chain::database* _db, std::string _ip, + uint32_t _zmq, uint32_t _rpc, std::string _user, std::string _password ); + + ~sidechain_net_manager() { db = nullptr; } + + void initialize_manager( graphene::chain::database* _db, std::string _ip, + uint32_t _zmq, uint32_t _rpc, std::string _user, std::string _password ); + + void update_tx_infos( const std::string& block_hash ); + + void update_tx_approvals(); + + void update_estimated_fee(); + + void send_btc_tx( const sidechain::bitcoin_transaction& trx ); + + bool connection_is_not_defined() const; + +private: + + void handle_block( const std::string& block_hash ); + + std::vector extract_info_from_block( const std::string& _block ); + + void update_transaction_status( std::vector trx_for_check ); + + std::set get_valid_vins( const std::string tx_hash ); + + inline uint64_t parse_amount(std::string raw); + + std::unique_ptr listener; + std::unique_ptr bitcoin_client; + graphene::chain::database* db; + +}; + +} diff --git a/libraries/sidechain/network/include/sidechain/network/zmq_listener.hpp b/libraries/sidechain/network/include/sidechain/network/zmq_listener.hpp new file mode 100644 index 000000000..d077bac71 --- /dev/null +++ b/libraries/sidechain/network/include/sidechain/network/zmq_listener.hpp @@ -0,0 +1,35 @@ +#pragma once + +#include +#include + +#include + +#include + +namespace sidechain { + +class zmq_listener +{ + +public: + + zmq_listener( std::string _ip, uint32_t _zmq ); + + bool connection_is_not_defined() const { return zmq_port == 0; } + + fc::signal block_received; + +private: + + void handle_zmq(); + std::vector receive_multipart(); + + std::string ip; + uint32_t zmq_port; + + zmq::context_t ctx; + zmq::socket_t socket; +}; + +} diff --git a/libraries/sidechain/network/sidechain_net_manager.cpp b/libraries/sidechain/network/sidechain_net_manager.cpp new file mode 100644 index 000000000..0deb59601 --- /dev/null +++ b/libraries/sidechain/network/sidechain_net_manager.cpp @@ -0,0 +1,202 @@ +#include +#include +#include + +#include +#include + +#include + +#include +#include + +#include + +namespace sidechain { + +sidechain_net_manager::sidechain_net_manager( graphene::chain::database* _db, std::string _ip, + uint32_t _zmq, uint32_t _rpc, std::string _user, std::string _password ) +{ + initialize_manager(_db, _ip, _zmq, _rpc, _user, _password ); +} + +void sidechain_net_manager::initialize_manager( graphene::chain::database* _db, std::string _ip, + uint32_t _zmq, uint32_t _rpc, std::string _user, std::string _password ) +{ + fc::http::connection conn; + try { + conn.connect_to( fc::ip::endpoint( fc::ip::address( _ip ), _rpc ) ); + } catch ( fc::exception e ) { + elog( "No BTC node running at ${ip} or wrong rpc port: ${port}", ("ip", _ip) ("port", _rpc) ); + FC_ASSERT( false ); + } + + listener = std::unique_ptr( new zmq_listener( _ip, _zmq ) ); + bitcoin_client = std::unique_ptr( new bitcoin_rpc_client( _ip, _rpc, _user, _password ) ); + db = _db; + + listener->block_received.connect([this]( const std::string& block_hash ) { + std::thread( &sidechain_net_manager::handle_block, this, block_hash ).detach(); + } ); + + db->send_btc_tx.connect([this]( const sidechain::bitcoin_transaction& trx ) { + std::thread( &sidechain_net_manager::send_btc_tx, this, trx ).detach(); + } ); +} + +void sidechain_net_manager::update_tx_infos( const std::string& block_hash ) +{ + std::string block = bitcoin_client->receive_full_block( block_hash ); + if( block != "" ) { + const auto& vins = extract_info_from_block( block ); + const auto& addr_idx = db->get_index_type().indices().get(); + for( const auto& v : vins ) { + const auto& addr_itr = addr_idx.find( v.address ); + FC_ASSERT( addr_itr != addr_idx.end() ); + db->i_w_info.insert_info_for_vin( prev_out{ v.out.hash_tx, v.out.n_vout, v.out.amount }, v.address, addr_itr->address.get_witness_script() ); + } + } +} + +void sidechain_net_manager::update_tx_approvals() +{ + std::vector trx_for_check; + const auto& confirmations_num = db->get_sidechain_params().confirmations_num; + + db->bitcoin_confirmations.safe_for([&]( btc_tx_confirmations_index::iterator itr_b, btc_tx_confirmations_index::iterator itr_e ){ + for(auto iter = itr_b; iter != itr_e; iter++) { + db->bitcoin_confirmations.modify( iter->transaction_id, [&]( bitcoin_transaction_confirmations& obj ) { + obj.count_block++; + }); + + if( iter->count_block == confirmations_num ) { + trx_for_check.push_back( iter->transaction_id ); + } + } + }); + + update_transaction_status( trx_for_check ); + +} + +void sidechain_net_manager::update_estimated_fee() +{ + db->estimated_feerate = bitcoin_client->receive_estimated_fee(); +} + +void sidechain_net_manager::send_btc_tx( const sidechain::bitcoin_transaction& trx ) +{ + std::set valid_vins; + for( const auto& v : trx.vin ) { + valid_vins.insert( v.prevout.hash ); + } + db->bitcoin_confirmations.insert( bitcoin_transaction_confirmations( trx.get_txid(), valid_vins ) ); + + FC_ASSERT( !bitcoin_client->connection_is_not_defined() ); + const auto tx_hex = fc::to_hex( pack( trx ) ); + + bitcoin_client->send_btc_tx( tx_hex ); +} + +bool sidechain_net_manager::connection_is_not_defined() const +{ + return listener->connection_is_not_defined() && bitcoin_client->connection_is_not_defined(); +} + +void sidechain_net_manager::handle_block( const std::string& block_hash ) +{ + update_tx_approvals(); + update_estimated_fee(); + update_tx_infos( block_hash ); +} + +std::vector sidechain_net_manager::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; + + const auto& addr_idx = db->get_index_type().indices().get(); + + 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(); + + if( !addr_idx.count( address_base58 ) ) continue; + + info_for_vin vin; + vin.out.hash_tx = tx.get_child("txid").get_value(); + vin.out.amount = parse_amount( o.second.get_child( "value" ).get_value() ); + vin.out.n_vout = o.second.get_child( "n" ).get_value(); + vin.address = address_base58; + result.push_back( vin ); + } + } + } + + return result; +} + +std::set sidechain_net_manager::get_valid_vins( const std::string tx_hash ) +{ + const auto& confirmations_obj = db->bitcoin_confirmations.find( fc::sha256( tx_hash ) ); + FC_ASSERT( confirmations_obj.valid() ); + + std::set valid_vins; + for( const auto& v : confirmations_obj->valid_vins ) { + auto confirmations = bitcoin_client->receive_confirmations_tx( v.str() ); + if( confirmations == 0 ) { + continue; + } + valid_vins.insert( v ); + } + return valid_vins; +} + +void sidechain_net_manager::update_transaction_status( std::vector trx_for_check ) +{ + const auto& confirmations_num = db->get_sidechain_params().confirmations_num; + + for( const auto& trx : trx_for_check ) { + auto confirmations = bitcoin_client->receive_confirmations_tx( trx.str() ); + db->bitcoin_confirmations.modify( trx, [&]( bitcoin_transaction_confirmations& obj ) { + obj.count_block = confirmations; + }); + + if( confirmations >= confirmations_num ) { + db->bitcoin_confirmations.modify( trx, [&]( bitcoin_transaction_confirmations& obj ) { + obj.confirmed = true; + }); + + } else if( confirmations == 0 ) { + auto is_in_mempool = bitcoin_client->receive_mempool_entry_tx( trx.str() ); + + std::set valid_vins; + if( !is_in_mempool ) { + valid_vins = get_valid_vins( trx.str() ); + } + + db->bitcoin_confirmations.modify( trx, [&]( bitcoin_transaction_confirmations& obj ) { + obj.missing = !is_in_mempool; + obj.valid_vins = valid_vins; + }); + } + } +} + +// Removes dot from amount output: "50.00000000" +inline uint64_t sidechain_net_manager::parse_amount(std::string raw) { + raw.erase(std::remove(raw.begin(), raw.end(), '.'), raw.end()); + return std::stoll(raw); +} + +} \ No newline at end of file diff --git a/libraries/sidechain/network/zmq_listener.cpp b/libraries/sidechain/network/zmq_listener.cpp new file mode 100644 index 000000000..eb697a758 --- /dev/null +++ b/libraries/sidechain/network/zmq_listener.cpp @@ -0,0 +1,45 @@ +#include +#include +#include + +namespace sidechain { + +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() +{ + socket.setsockopt( ZMQ_SUBSCRIBE, "hashblock", 0 ); + socket.connect( "tcp://" + ip + ":" + std::to_string( zmq_port ) ); + + while ( true ) { + auto msg = receive_multipart(); + const auto header = std::string( static_cast( msg[0].data() ), msg[0].size() ); + const auto hash = boost::algorithm::hex( std::string( static_cast( msg[1].data() ), msg[1].size() ) ); + + block_received( hash ); + } +} + +} \ No newline at end of file diff --git a/libraries/sidechain/primary_wallet_vout_manager.cpp b/libraries/sidechain/primary_wallet_vout_manager.cpp new file mode 100644 index 000000000..c7c9ccaa1 --- /dev/null +++ b/libraries/sidechain/primary_wallet_vout_manager.cpp @@ -0,0 +1,136 @@ +#include +#include +#include +#include + + +namespace sidechain { + +bool primary_wallet_vout_manager::is_max_vouts() const +{ + const auto& PW_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + return !( ( PW_vout_idx.size() - 1 ) <= db.get_sidechain_params().maximum_unconfirmed_vouts ); +} + +fc::optional< primary_wallet_vout_object > primary_wallet_vout_manager::get_latest_unused_vout() const +{ + const auto& PW_vout_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto itr = PW_vout_idx.end(); + FC_ASSERT( itr != PW_vout_idx.begin() ); + + itr--; + if( itr->used ) + return fc::optional< primary_wallet_vout_object >(); + return fc::optional< primary_wallet_vout_object > (*itr); +} + +fc::optional< primary_wallet_vout_object > primary_wallet_vout_manager::get_vout( fc::sha256 hash_id ) const +{ + const auto& PW_vout_by_hash_id = db.get_index_type().indices().get< graphene::chain::by_hash_id >(); + const auto& itr_hash_id = PW_vout_by_hash_id.find( hash_id ); + if( itr_hash_id == PW_vout_by_hash_id.end() ) + return fc::optional< primary_wallet_vout_object >(); + return fc::optional< primary_wallet_vout_object >( *itr_hash_id ); +} + +void primary_wallet_vout_manager::create_new_vout( const sidechain::prev_out& out ) +{ + const auto& next_pw_id = db.get_index_type().get_next_id().instance(); + db.create([&]( primary_wallet_vout_object& obj ) { + obj.vout = out; + obj.hash_id = create_hash_id( out.hash_tx, out.n_vout, next_pw_id ); + obj.confirmed = false; + obj.used = false; + }); +} + +void primary_wallet_vout_manager::delete_vouts_after( fc::sha256 hash_id ) +{ + const auto& PW_vout_by_id = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto vout_id = get_vout_id( hash_id ); + if( !vout_id.valid() ) + return; + + auto itr = PW_vout_by_id.find( *vout_id ); + itr++; + + while( itr != PW_vout_by_id.end() ) + { + auto temp = itr; + itr++; + db.remove( *temp ); + } +} + +void primary_wallet_vout_manager::confirm_vout( fc::sha256 hash_id ) +{ + const auto& PW_vout_by_id = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto vout_id = get_vout_id( hash_id ); + FC_ASSERT( vout_id.valid() ); + + auto itr = PW_vout_by_id.find( *vout_id ); + + db.modify(*itr, [&]( primary_wallet_vout_object& PW_vout ) { + PW_vout.confirmed = true; + }); + + if( itr != PW_vout_by_id.begin() ){ + itr--; + FC_ASSERT( itr->confirmed == true ); + db.remove(*itr); + } +} + +void primary_wallet_vout_manager::mark_as_used_vout( fc::sha256 hash_id ) +{ + const auto& PW_vout_by_id = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto vout_id = get_vout_id( hash_id ); + FC_ASSERT( vout_id.valid() ); + + auto itr = PW_vout_by_id.find( *vout_id ); + + db.modify(*itr, [&]( primary_wallet_vout_object& PW_vout ) { + PW_vout.used = true; + }); + + if( itr != PW_vout_by_id.begin() ){ + itr--; + FC_ASSERT( itr->used == true ); + } +} + +void primary_wallet_vout_manager::mark_as_unused_vout( fc::sha256 hash_id ) +{ + const auto& PW_vout_by_id = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto vout_id = get_vout_id( hash_id ); + FC_ASSERT( vout_id.valid() ); + + auto itr = PW_vout_by_id.find( *vout_id ); + + db.modify(*itr, [&]( primary_wallet_vout_object& PW_vout ) { + PW_vout.used = false; + }); +} + +fc::optional< graphene::chain::primary_wallet_vout_id_type > primary_wallet_vout_manager::get_vout_id( fc::sha256 hash_id ) const +{ + const auto& PW_vout_by_hash_id = db.get_index_type().indices().get< graphene::chain::by_hash_id >(); + const auto& itr_hash_id = PW_vout_by_hash_id.find( hash_id ); + if( itr_hash_id == PW_vout_by_hash_id.end() ) + return fc::optional< graphene::chain::primary_wallet_vout_id_type >(); + return fc::optional< graphene::chain::primary_wallet_vout_id_type >( itr_hash_id->get_id() ); +} + +fc::sha256 primary_wallet_vout_manager::create_hash_id( const std::string& hash_tx, const uint32_t& n_vout, const uint64_t& id ) const +{ + std::stringstream ss; + ss << std::hex << id; + std::string id_hex = std::string( 16 - ss.str().size(), '0' ) + ss.str(); + + std::string hash_str = fc::sha256::hash( hash_tx + std::to_string( n_vout ) ).str(); + std::string final_hash_id = std::string( hash_str.begin(), hash_str.begin() + 48 ) + id_hex; + + return fc::sha256( final_hash_id ); +} + +} \ No newline at end of file diff --git a/libraries/sidechain/segwit_addr.cpp b/libraries/sidechain/segwit_addr.cpp new file mode 100644 index 000000000..ce7b33adf --- /dev/null +++ b/libraries/sidechain/segwit_addr.cpp @@ -0,0 +1,81 @@ +/* Copyright (c) 2017 Pieter Wuille + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ + +#include +#include + +namespace +{ + +typedef std::vector data; + +/** Convert from one power-of-2 number base to another. */ +template +bool convertbits(data& out, const data& 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; +} + +} + +namespace sidechain { namespace segwit_addr { + +/** Decode a SegWit address. */ +std::pair decode(const std::string& hrp, const std::string& addr) { + std::pair dec = sidechain::bech32::Decode(addr); + if (dec.first != hrp || dec.second.size() < 1) return std::make_pair(-1, data()); + data conv; + if (!convertbits<5, 8, false>(conv, data(dec.second.begin() + 1, dec.second.end())) || + conv.size() < 2 || conv.size() > 40 || dec.second[0] > 16 || (dec.second[0] == 0 && + conv.size() != 20 && conv.size() != 32)) { + return std::make_pair(-1, data()); + } + return std::make_pair(dec.second[0], conv); +} + +/** Encode a SegWit address. */ +std::string encode(const std::string& hrp, int witver, const data& witprog) { + data enc; + enc.push_back(witver); + convertbits<8, 5, true>(enc, witprog); + std::string ret = sidechain::bech32::Encode(hrp, enc); + if (decode(hrp, ret).first == -1) return ""; + return ret; +} + +} } diff --git a/libraries/sidechain/sidechain_condensing_tx.cpp b/libraries/sidechain/sidechain_condensing_tx.cpp new file mode 100644 index 000000000..cb742727a --- /dev/null +++ b/libraries/sidechain/sidechain_condensing_tx.cpp @@ -0,0 +1,107 @@ +#include + +namespace sidechain { + +sidechain_condensing_tx::sidechain_condensing_tx( const std::vector& vin_info, const std::vector& vout_info ) +{ + create_vins_for_condensing_tx( vin_info ); + create_vouts_for_condensing_tx( vout_info ); +} + +void sidechain_condensing_tx::create_pw_vin( const info_for_vin& vin_info, bool front ) +{ + bytes witness_script_temp = {0x22}; + witness_script_temp.insert( witness_script_temp.end(), vin_info.script.begin(), vin_info.script.end() ); + tb.add_in( payment_type::P2WSH, fc::sha256( vin_info.out.hash_tx ), vin_info.out.n_vout, witness_script_temp, front ); + + amount_vins += vin_info.out.amount; +} + +void sidechain_condensing_tx::create_pw_vout( const uint64_t amount, const bytes& wit_script_out, bool front ) +{ + tb.add_out( payment_type::P2WSH, amount, bytes(wit_script_out.begin() + 2, wit_script_out.end()), front ); +} + +void sidechain_condensing_tx::create_vins_for_condensing_tx( const std::vector& vin_info ) +{ + for( const auto& info : vin_info ) { + bytes witness_script_temp = {0x22}; + witness_script_temp.insert( witness_script_temp.end(), info.script.begin(), info.script.end() ); + tb.add_in( payment_type::P2SH_WSH, fc::sha256( info.out.hash_tx ), info.out.n_vout, witness_script_temp ); + amount_vins += info.out.amount; + count_transfer_vin++; + } +} + +void sidechain_condensing_tx::create_vouts_for_condensing_tx( const std::vector& vout_info ) +{ + for( const auto& info : vout_info ) { + tb.add_out_all_type( info.amount, info.address ); + amount_transfer_to_bitcoin += info.amount; + count_transfer_vout++; + } +} + +void sidechain_condensing_tx::create_vouts_for_witness_fee( const accounts_keys& witness_active_keys, bool front ) +{ + for( auto& key : witness_active_keys ) { + tb.add_out( payment_type::P2PK, 0, key.second, front ); + count_witness_vout++; + } +} + +uint64_t sidechain_condensing_tx::get_estimate_tx_size( bitcoin_transaction tx, size_t number_witness ) +{ + bytes temp_sig(72, 0x00); + bytes temp_key(34, 0x00); + bytes temp_script(3, 0x00); + for(size_t i = 0; i < number_witness; i++) { + temp_script.insert(temp_script.begin() + 1, temp_key.begin(), temp_key.end()); + } + + std::vector temp_scriptWitness = { {},{temp_sig},{temp_sig},{temp_sig},{temp_sig},{temp_sig},{temp_script} }; + for( auto& vin : tx.vin ) { + vin.scriptWitness = temp_scriptWitness; + } + + return tx.get_vsize(); +} + +void sidechain_condensing_tx::subtract_fee( const uint64_t& fee, const uint16_t& witnesses_percentage ) +{ + bitcoin_transaction tx = get_transaction(); + + uint64_t fee_size = fee / ( count_transfer_vin + count_transfer_vout ); + if( fee % ( count_transfer_vin + count_transfer_vout ) != 0 ) { + fee_size += 1; + } + + bool is_pw_vin = tx.vout.size() > ( count_witness_vout + count_transfer_vout ); + if( is_pw_vin ) { + tx.vout[0].value = tx.vout[0].value - fee_size * count_transfer_vin; + } + + uint64_t fee_witnesses = 0; + size_t offset = is_pw_vin ? 1 + count_witness_vout : count_witness_vout; + for( ; offset < tx.vout.size(); offset++ ) { + uint64_t amount_without_fee_size = tx.vout[offset].value - fee_size; + uint64_t amount_fee_witness = ( amount_without_fee_size * witnesses_percentage ) / GRAPHENE_100_PERCENT; + tx.vout[offset].value = amount_without_fee_size; + tx.vout[offset].value -= amount_fee_witness; + fee_witnesses += amount_fee_witness; + } + + if( count_witness_vout > 0 ) { + uint64_t fee_witness = fee_witnesses / count_witness_vout; + size_t limit = is_pw_vin ? 1 + count_witness_vout : count_witness_vout; + size_t offset = is_pw_vin ? 1 : 0; + FC_ASSERT( fee_witness > 0 ); + for( ; offset < limit; offset++ ) { + tx.vout[offset].value += fee_witness; + } + } + + tb = std::move( bitcoin_transaction_builder( tx ) ); +} + +} diff --git a/libraries/sidechain/sidechain_proposal_checker.cpp b/libraries/sidechain/sidechain_proposal_checker.cpp new file mode 100644 index 000000000..214a1808a --- /dev/null +++ b/libraries/sidechain/sidechain_proposal_checker.cpp @@ -0,0 +1,214 @@ +#include +#include + +#include +#include +#include +#include +#include +#include + +namespace sidechain { + +bool sidechain_proposal_checker::check_bitcoin_transaction_send_operation( const bitcoin_transaction_send_operation& op ) +{ + bool info_for_pw_vin = check_info_for_pw_vin( op.pw_vin ); + bool info_for_vins = check_info_for_vins( op.vins ); + bool info_for_vouts = check_info_for_vouts( op.vouts ); + bool transaction = check_transaction( op ); + + return info_for_pw_vin && info_for_vins && info_for_vouts && transaction; +} + +bool sidechain_proposal_checker::check_reuse_pw_vin( const fc::sha256& pw_vin ) +{ + const auto& pw_vin_status = pw_vin_ident.insert( pw_vin ); + if( !pw_vin_status.second ) { + return false; + } + return true; +} + +bool sidechain_proposal_checker::check_reuse_user_vins( const std::vector& user_vin_identifiers ) +{ + for( const auto& vin : user_vin_identifiers ) { + const auto& user_vin_status = user_vin_ident.insert( vin ); + if( !user_vin_status.second ) { + return false; + } + } + return true; +} + +bool sidechain_proposal_checker::check_reuse_vouts( const std::vector& user_vout_ids ) +{ + for( const auto& vout : user_vout_ids ) { + const auto& user_vout_status = vout_ids.insert( vout ); + if( !user_vout_status.second ) { + return false; + } + } + return true; +} + +bool sidechain_proposal_checker::check_reuse( const operation& op ) +{ + fc::sha256 pw_vin_identifier; + std::vector user_vin_identifiers; + std::vector user_vout_ids; + + auto get_bto_tx_info = [ & ]( fc::sha256 trx_id ) { + const auto& bto_itr_idx = db.get_index_type().indices().get(); + const auto& bto_itr = bto_itr_idx.find( trx_id ); + if( bto_itr == bto_itr_idx.end() ) { + return false; + } + pw_vin_identifier = bto_itr->pw_vin; + user_vout_ids = bto_itr->vouts; + user_vin_identifiers = bto_itr->vins; + + return true; + }; + + if( op.which() == operation::tag::value ) { + bitcoin_transaction_send_operation btc_tx_send_op = op.get(); + pw_vin_identifier = btc_tx_send_op.pw_vin.identifier; + user_vout_ids = btc_tx_send_op.vouts; + for( const auto& vin : btc_tx_send_op.vins ) { + user_vin_identifiers.push_back( vin.identifier ); + } + } else if ( op.which() == operation::tag::value ) { + bitcoin_issue_operation btc_issue_op = op.get(); + for( const auto& id : btc_issue_op.transaction_ids ) { + if ( !get_bto_tx_info( id ) ) + return false; + } + } else if ( op.which() == operation::tag::value ) { + bitcoin_transaction_revert_operation btc_tx_revert_op = op.get(); + for( auto trx_info: btc_tx_revert_op.transactions_info ) { + if ( !get_bto_tx_info( trx_info.transaction_id ) ) + return false; + } + } + + return check_reuse_pw_vin( pw_vin_identifier ) && + check_reuse_user_vins( user_vin_identifiers ) && + check_reuse_vouts( user_vout_ids ); +} + +bool sidechain_proposal_checker::check_info_for_pw_vin( const info_for_vin& info_for_vin ) +{ + const auto& prevout = db.pw_vout_manager.get_vout( info_for_vin.identifier ); + const auto& pw_address = db.get_latest_PW().address; + if( !prevout.valid() || info_for_vin.out != prevout->vout || + info_for_vin.address != pw_address.get_address() || info_for_vin.script != pw_address.get_witness_script() ) { + return false; + } + return true; +} + +bool sidechain_proposal_checker::check_info_for_vins( const std::vector& info_for_vins ) +{ + for( const auto& vin : info_for_vins ) { + const auto& v = db.i_w_info.find_info_for_vin( vin.identifier ); + if( !v.valid() || *v != vin ) { + return false; + } + } + return true; +} + +bool sidechain_proposal_checker::check_info_for_vouts( const std::vector& info_for_vout_ids ) +{ + const auto& info_for_vout_idx = db.get_index_type().indices().get(); + for( const auto& id : info_for_vout_ids ) { + const auto& itr = info_for_vout_idx.find( id ); + if( itr == info_for_vout_idx.end() ) { + return false; + } + } + return true; +} + +bool sidechain_proposal_checker::check_transaction( const bitcoin_transaction_send_operation& btc_trx_op ) +{ + std::vector info_vouts; + const auto& info_for_vout_idx = db.get_index_type().indices().get(); + for( const auto& vout_id : btc_trx_op.vouts ) { + const auto& vout_itr = info_for_vout_idx.find( vout_id ); + if( vout_itr == info_for_vout_idx.end() ) { + return false; + } + info_vouts.push_back( *vout_itr ); + } + + const auto& temp_full_tx = db.create_btc_transaction( btc_trx_op.vins, info_vouts, btc_trx_op.pw_vin ); + + if( temp_full_tx.first != btc_trx_op.transaction || temp_full_tx.second != btc_trx_op.fee_for_size ) { + return false; + } + + return true; +} + +bool sidechain_proposal_checker::check_witness_opportunity_to_approve( const witness_object& current_witness, const proposal_object& proposal ) +{ + auto is_active_witness = [ & ]() { + return db.get_global_properties().active_witnesses.find( current_witness.id ) != db.get_global_properties().active_witnesses.end(); + }; + + // Checks can witness approve this proposal or not + auto does_the_witness_have_authority = [ & ]() { + const auto& accounts_index = db.get_index_type().indices().get(); + auto account_pBTC_issuer = accounts_index.find( db.get_sidechain_account_id() ); + + return account_pBTC_issuer->owner.account_auths.count( current_witness.witness_account ) && + !proposal.available_active_approvals.count( current_witness.witness_account ); + }; + + return is_active_witness() && does_the_witness_have_authority(); +} + +bool sidechain_proposal_checker::check_witnesses_keys( const witness_object& current_witness, const proposal_object& proposal ) +{ + bitcoin_transaction_send_operation op = proposal.proposed_transaction.operations.back().get(); + + auto vins = op.vins; + if( op.pw_vin.identifier.str().compare( 0, 48, SIDECHAIN_NULL_VIN_IDENTIFIER ) != 0 ) { + vins.insert( vins.begin(), op.pw_vin ); + } + + const auto& bitcoin_address_idx = db.get_index_type().indices().get< by_address >(); + + for ( const auto& info_vins : vins ) { + const auto& address_itr = bitcoin_address_idx.find( info_vins.address ); + if ( address_itr != bitcoin_address_idx.end() ) { + const auto& witness_key = current_witness.signing_key; + const auto& witnesses_keys = address_itr->address.witnesses_keys; + const auto& witnesses_keys_itr = witnesses_keys.find( current_witness.witness_account ); + if ( witnesses_keys_itr == witnesses_keys.end() || witnesses_keys_itr->second != witness_key ) { + return false; + } + } + } + + return true; +} + +bool sidechain_proposal_checker::check_bitcoin_transaction_revert_operation( const bitcoin_transaction_revert_operation& op ) +{ + const auto& btc_trx_idx = db.get_index_type().indices().get(); + + for( auto trx_info: op.transactions_info ) + { + auto value = db.bitcoin_confirmations.find( trx_info.transaction_id ); + if( !value.valid() || + btc_trx_idx.find( value->transaction_id ) == btc_trx_idx.end() || + trx_info != revert_trx_info( value->transaction_id, value->valid_vins ) ) + return false; + } + + return !op.transactions_info.empty(); +} + +} diff --git a/libraries/sidechain/sign_bitcoin_transaction.cpp b/libraries/sidechain/sign_bitcoin_transaction.cpp new file mode 100644 index 000000000..2c22d2b1f --- /dev/null +++ b/libraries/sidechain/sign_bitcoin_transaction.cpp @@ -0,0 +1,125 @@ +#include +#include + +namespace sidechain { + +fc::sha256 get_signature_hash( const bitcoin_transaction& tx, const bytes& scriptCode, int64_t amount, + size_t in_index, int hash_type, bool is_witness ) +{ + fc::datastream ps; + if ( is_witness ) + pack_tx_witness_signature( ps, scriptCode, tx, in_index, amount, hash_type ); + else + pack_tx_signature( ps, scriptCode, tx, in_index, hash_type ); + + std::vector vec( ps.tellp() ); + if ( !vec.empty() ) { + fc::datastream ds( vec.data(), vec.size() ); + if ( is_witness ) + pack_tx_witness_signature( ds, scriptCode, tx, in_index, amount, hash_type ); + else + pack_tx_signature( ds, scriptCode, tx, in_index, hash_type ); + } + + return fc::sha256::hash( fc::sha256::hash( vec.data(), vec.size() ) ); +} + +std::vector privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign ) +{ + bytes sig; + sig.resize( 72 ); + int sig_len = sig.size(); + + FC_ASSERT( secp256k1_ecdsa_sign( + context_sign, + reinterpret_cast( hash.data() ), + reinterpret_cast( sig.data() ), + &sig_len, + reinterpret_cast( privkey.data() ), + secp256k1_nonce_function_rfc6979, + nullptr ) ); // TODO: replace assert with exception + + sig.resize( sig_len ); + + return sig; +} + +std::vector sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const bytes& privkey, + const secp256k1_context_t* context_sign, int hash_type ) +{ + FC_ASSERT( tx.vin.size() == redeem_scripts.size() && tx.vin.size() == amounts.size() ); + FC_ASSERT( !privkey.empty() ); + + std::vector< bytes > signatures; + for( size_t i = 0; i < tx.vin.size(); i++ ) { + const auto sighash = get_signature_hash( tx, redeem_scripts[i], static_cast( amounts[i] ), i, hash_type, true ); + auto sig = privkey_sign( privkey, sighash, context_sign ); + sig.push_back( static_cast( hash_type ) ); + + signatures.push_back( sig ); + } + return signatures; +} + +void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector& redeem_scripts ) +{ + FC_ASSERT( tx.vin.size() == redeem_scripts.size() ); + + for( size_t i = 0; i < tx.vin.size(); i++ ) { + tx.vin[i].scriptWitness.insert( tx.vin[i].scriptWitness.begin(), bytes() ); // Bitcoin workaround CHECKMULTISIG bug + tx.vin[i].scriptWitness.push_back( redeem_scripts[i] ); + } +} + +bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context ) +{ + std::vector sig_temp( sig.begin(), sig.end() ); + std::vector pubkey_temp( pubkey.begin(), pubkey.end() ); + std::vector msg_temp( msg.begin(), msg.end() ); + + int result = secp256k1_ecdsa_verify( context, msg_temp.data(), sig_temp.data(), sig_temp.size(), pubkey_temp.data(), pubkey_temp.size() ); + return result == 1; +} + +std::vector> sort_sigs( const bitcoin_transaction& tx, const std::vector& redeem_scripts, + const std::vector& amounts, const secp256k1_context_t* context ) +{ + FC_ASSERT( redeem_scripts.size() == amounts.size() ); + + using data = std::pair; + struct comp { + bool operator() (const data& lhs, const data& rhs) const { return lhs.first < rhs.first; } + }; + + std::vector> new_stacks; + + for( size_t i = 0; i < redeem_scripts.size(); i++ ) { + const std::vector& keys = get_pubkey_from_redeemScript( redeem_scripts[i] ); + const auto& sighash = get_signature_hash( tx, redeem_scripts[i], static_cast( amounts[i] ), i, 1, true ).str(); + bytes sighash_temp( parse_hex( sighash ) ); + + std::vector stack( tx.vin[i].scriptWitness ); + std::vector marker( tx.vin[i].scriptWitness.size(), false ); + std::set sigs; + + for( size_t j = 0; j < keys.size(); j++ ) { + for( size_t l = 0; l < stack.size(); l++ ) { + if( !verify_sig( stack[l], keys[j], sighash_temp, context ) || marker[l] ) + continue; + sigs.insert(std::make_pair(j, stack[l])); + marker[l] = true; + break; + } + } + + std::vector temp_sig; + for( auto s : sigs ) { + temp_sig.push_back( s.second ); + } + new_stacks.push_back( temp_sig ); + } + return new_stacks; +} + +} diff --git a/libraries/sidechain/utils.cpp b/libraries/sidechain/utils.cpp new file mode 100644 index 000000000..b13c63fb8 --- /dev/null +++ b/libraries/sidechain/utils.cpp @@ -0,0 +1,35 @@ +#include + +namespace sidechain { + +bytes parse_hex( const std::string& str ) +{ + bytes vec( str.size() / 2 ); + fc::from_hex( str, vec.data(), vec.size() ); + return vec; +} + +std::vector get_pubkey_from_redeemScript( bytes script ) +{ + FC_ASSERT( script.size() >= 37 ); + + script.erase( script.begin() ); + script.erase( script.end() - 2, script.end() ); + + std::vector result; + uint64_t count = script.size() / 34; + for( size_t i = 0; i < count; i++ ) { + result.push_back( bytes( script.begin() + (34 * i) + 1, script.begin() + (34 * (i + 1)) ) ); + } + return result; +} + +bytes public_key_data_to_bytes( const fc::ecc::public_key_data& key ) +{ + bytes result; + result.resize( key.size() ); + std::copy( key.begin(), key.end(), result.begin() ); + return result; +} + +} diff --git a/libraries/wallet/include/graphene/wallet/wallet.hpp b/libraries/wallet/include/graphene/wallet/wallet.hpp index 0ba0782b5..81ccac986 100644 --- a/libraries/wallet/include/graphene/wallet/wallet.hpp +++ b/libraries/wallet/include/graphene/wallet/wallet.hpp @@ -767,6 +767,8 @@ class wallet_api string memo, bool broadcast = false); + signed_transaction withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast = false); + /** * This method works just like transfer, except it always broadcasts and * returns the transaction ID along with the signed transaction. @@ -1795,6 +1797,20 @@ class wallet_api rock_paper_scissors_gesture gesture, bool broadcast); + + /** Create bitcoin multisig address for sidechain + * @param payer the id of fee payer + * @param owner the id of address owner + * @return the signed version of the transaction + */ + signed_transaction create_bitcoin_address(string payer, string owner, bool broadcast); + + /** Get all account's bitcoin addresses + * @param account_name_or_id the name or id of the account to provide information of bitcoin addresses + * @return list of bitcoin addresses + */ + vector get_bitcoin_addresses(string account_name_or_id) const; + void dbg_make_uia(string creator, string symbol); void dbg_make_mia(string creator, string symbol); void dbg_push_blocks( std::string src_filename, uint32_t count ); @@ -2031,6 +2047,8 @@ FC_API( graphene::wallet::wallet_api, (tournament_join) (tournament_leave) (rps_throw) + (create_bitcoin_address) + (get_bitcoin_addresses) (get_upcoming_tournaments) (get_tournaments) (get_tournaments_by_state) @@ -2041,4 +2059,5 @@ FC_API( graphene::wallet::wallet_api, (get_binned_order_book) (get_matched_bets_for_bettor) (get_all_matched_bets_for_bettor) + (withdraw_pBTC) ) diff --git a/libraries/wallet/wallet.cpp b/libraries/wallet/wallet.cpp index 74c285bfa..9baaebc38 100644 --- a/libraries/wallet/wallet.cpp +++ b/libraries/wallet/wallet.cpp @@ -273,9 +273,10 @@ class wallet_api_impl private: void claim_registered_account(const account_object& account) { + std::vector import_keys; auto it = _wallet.pending_account_registrations.find( account.name ); FC_ASSERT( it != _wallet.pending_account_registrations.end() ); - for (const std::string& wif_key : it->second) + for (const std::string& wif_key : it->second) { if( !import_key( account.name, wif_key ) ) { // somebody else beat our pending registration, there is @@ -288,8 +289,23 @@ class wallet_api_impl // possibility of migrating to a fork where the // name is available, the user can always // manually re-register) + } else { + import_keys.push_back( wif_key ); } + } _wallet.pending_account_registrations.erase( it ); + + for( const auto& k : import_keys ) { + fc::optional optional_private_key = wif_to_key( k ); + if (!optional_private_key) + FC_THROW("Invalid private key"); + string shorthash = detail::address_to_shorthash(optional_private_key->get_public_key()); + copy_wallet_file( "before-import-key-" + shorthash ); + + save_wallet_file(); + copy_wallet_file( "after-import-key-" + shorthash ); + } + } // after a witness registration succeeds, this saves the private key in the wallet permanently @@ -2392,6 +2408,23 @@ class wallet_api_impl return sign_transaction(tx, broadcast); } FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) } + signed_transaction withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast) + { try { + FC_ASSERT( !is_locked() ); + + withdraw_pbtc_operation withdraw_pbtc_op; + withdraw_pbtc_op.payer = payer; + withdraw_pbtc_op.data = to; + withdraw_pbtc_op.amount = amount; + + signed_transaction tx; + tx.operations.push_back(withdraw_pbtc_op); + set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees); + tx.validate(); + + return sign_transaction(tx, broadcast); + } FC_CAPTURE_AND_RETHROW( (payer)(to)(amount)(broadcast) ) } + signed_transaction issue_asset(string to_account, string amount, string symbol, string memo, bool broadcast = false) { @@ -3870,6 +3903,12 @@ signed_transaction wallet_api::transfer(string from, string to, string amount, { return my->transfer(from, to, amount, asset_symbol, memo, broadcast); } + +signed_transaction wallet_api::withdraw_pBTC(account_id_type payer, string to, uint64_t amount, bool broadcast) +{ + return my->withdraw_pBTC(payer, to, amount, broadcast); +} + signed_transaction wallet_api::create_asset(string issuer, string symbol, uint8_t precision, @@ -5756,6 +5795,28 @@ signed_transaction wallet_api::rps_throw(game_id_type game_id, return my->sign_transaction( tx, broadcast ); } +signed_transaction wallet_api::create_bitcoin_address( string payer, string owner, bool broadcast ) +{ + FC_ASSERT( !is_locked() ); + + bitcoin_address_create_operation op; + op.payer = get_account_id(payer); + op.owner = get_account_id(owner); + + signed_transaction tx; + tx.operations = { op }; + my->set_operation_fees( tx, my->_remote_db->get_global_properties().parameters.current_fees ); + tx.validate(); + + return my->sign_transaction( tx, broadcast ); +} + +vector wallet_api::get_bitcoin_addresses(string account_name_or_id) const +{ + return my->_remote_db->get_bitcoin_addresses( get_account_id(account_name_or_id) ); +} + + // default ctor necessary for FC_REFLECT signed_block_with_info::signed_block_with_info() { diff --git a/programs/js_operation_serializer/main.cpp b/programs/js_operation_serializer/main.cpp index 8994b36b5..baa048d67 100644 --- a/programs/js_operation_serializer/main.cpp +++ b/programs/js_operation_serializer/main.cpp @@ -43,6 +43,10 @@ #include #include #include +#include +#include +#include +#include #include #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 57a451aa8..5874d0c98 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -41,4 +41,8 @@ file(GLOB RANDOM_SOURCES "random/*.cpp") add_executable( random_test ${RANDOM_SOURCES} ${COMMON_SOURCES} ) target_link_libraries( random_test graphene_chain graphene_app graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) +file(GLOB SIDECHAIN_TESTS "sidechain_tests/*.cpp") +add_executable( sidechain_test ${SIDECHAIN_TESTS} ${COMMON_SOURCES} ) +target_link_libraries( sidechain_test graphene_chain graphene_app graphene_egenesis_none fc ${PLATFORM_SPECIFIC_LIBS} ) + add_subdirectory( generate_empty_blocks ) diff --git a/tests/common/database_fixture.hpp b/tests/common/database_fixture.hpp index 200d1897a..c5d1f94b7 100644 --- a/tests/common/database_fixture.hpp +++ b/tests/common/database_fixture.hpp @@ -380,3 +380,34 @@ processed_transaction _push_transaction( database& db, const signed_transaction& } } } + +namespace sidechain { + +class test_environment +{ + +public: + + test_environment( database& db ) + { + + for( size_t i = 1; i < 6; i++ ) { + prev_out out{ std::string( 64, std::to_string( i )[0] ), static_cast( i ), static_cast( i * 10000 ) }; + db.i_w_info.insert_info_for_vin( out, std::to_string( i ), { 0x00, 0x01, 0x02 } ); + db.i_w_info.insert_info_for_vout( account_id_type( i ), "2Mt57VSFqBe7UpDad9QaYHev21E1VscAZMU", i * 5000 ); + } + + vins = db.i_w_info.get_info_for_vins(); + pw_vin = *db.i_w_info.get_info_for_pw_vin(); + vouts = db.i_w_info.get_info_for_vouts(); + full_tx = db.create_btc_transaction( vins, vouts, pw_vin ); + } + + std::vector vins; + info_for_vin pw_vin; + std::vector vouts; + full_btc_transaction full_tx; + +}; + +} \ No newline at end of file diff --git a/tests/sidechain_tests/bitcoin_address_tests.cpp b/tests/sidechain_tests/bitcoin_address_tests.cpp new file mode 100644 index 000000000..e5437d42e --- /dev/null +++ b/tests/sidechain_tests/bitcoin_address_tests.cpp @@ -0,0 +1,134 @@ +#include +#include + +using namespace sidechain; + +BOOST_AUTO_TEST_SUITE( bitcoin_address_tests ) + +fc::ecc::public_key_data create_public_key_data( const std::vector& public_key ) +{ + FC_ASSERT( public_key.size() == 33 ); + fc::ecc::public_key_data key; + for(size_t i = 0; i < 33; i++) { + key.at(i) = public_key[i]; + } + return key; +} + +BOOST_AUTO_TEST_CASE( addresses_type_test ) +{ + // public_key + std::string compressed( "03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22" ); + BOOST_CHECK( bitcoin_address( compressed ).get_type() == payment_type::P2PK ); + + std::string uncompressed( "04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904" ); + BOOST_CHECK( bitcoin_address( uncompressed ).get_type() == payment_type::NULLDATA ); + + + // segwit_address + std::string p2wpkh_mainnet( "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" ); + BOOST_CHECK( bitcoin_address( p2wpkh_mainnet ).get_type() == payment_type::P2WPKH ); + + std::string p2wpkh_testnet( "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" ); + BOOST_CHECK( bitcoin_address( p2wpkh_testnet ).get_type() == payment_type::P2WPKH ); + + std::string p2wsh( "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9" ); + BOOST_CHECK( bitcoin_address( p2wsh ).get_type() == payment_type::P2WSH ); + + + // base58 + std::string p2pkh_mainnet( "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem" ); + BOOST_CHECK( bitcoin_address( p2pkh_mainnet ).get_type() == payment_type::P2PKH ); + + std::string p2pkh_testnet( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + BOOST_CHECK( bitcoin_address( p2pkh_testnet ).get_type() == payment_type::P2PKH ); + + std::string p2sh_mainnet( "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" ); + BOOST_CHECK( bitcoin_address( p2sh_mainnet ).get_type() == payment_type::P2SH ); + + std::string p2sh_testnet( "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc" ); + BOOST_CHECK( bitcoin_address( p2sh_testnet ).get_type() == payment_type::P2SH ); +} + +BOOST_AUTO_TEST_CASE( addresses_raw_test ) +{ + // public_key + std::string compressed( "03df51984d6b8b8b1cc693e239491f77a36c9e9dfe4a486e9972a18e03610a0d22" ); + bytes standard_compressed( parse_hex( compressed ) ); + BOOST_CHECK( bitcoin_address( compressed ).get_raw_address() == standard_compressed ); + + std::string uncompressed( "04fe53c78e36b86aae8082484a4007b706d5678cabb92d178fc95020d4d8dc41ef44cfbb8dfa7a593c7910a5b6f94d079061a7766cbeed73e24ee4f654f1e51904" ); + BOOST_CHECK( bitcoin_address( uncompressed ).get_raw_address() == bytes() ); + + + // segwit_address + std::string p2wpkh_mainnet( "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t4" ); + bytes standard_p2wpkh_mainnet( parse_hex( "751e76e8199196d454941c45d1b3a323f1433bd6" ) ); + BOOST_CHECK( bitcoin_address( p2wpkh_mainnet ).get_raw_address() == standard_p2wpkh_mainnet ); + + std::string p2wpkh_testnet( "tb1qw508d6qejxtdg4y5r3zarvary0c5xw7kxpjzsx" ); + bytes standard_p2wpkh_testnet( parse_hex( "751e76e8199196d454941c45d1b3a323f1433bd6" ) ); + BOOST_CHECK( bitcoin_address( p2wpkh_testnet ).get_raw_address() == standard_p2wpkh_testnet ); + + std::string p2wsh( "bc1qc7slrfxkknqcq2jevvvkdgvrt8080852dfjewde450xdlk4ugp7szw5tk9" ); + bytes standard_p2wsh( parse_hex( "c7a1f1a4d6b4c1802a59631966a18359de779e8a6a65973735a3ccdfdabc407d" ) ); + BOOST_CHECK( bitcoin_address( p2wsh ).get_raw_address() == standard_p2wsh ); + + + // base58 + std::string p2pkh_mainnet( "17VZNX1SN5NtKa8UQFxwQbFeFc3iqRYhem" ); + bytes standard_p2pkh_mainnet( parse_hex( "47376c6f537d62177a2c41c4ca9b45829ab99083" ) ); + BOOST_CHECK( bitcoin_address( p2pkh_mainnet ).get_raw_address() == standard_p2pkh_mainnet ); + + std::string p2pkh_testnet( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + bytes standard_p2pkh_testnet( parse_hex( "243f1394f44554f4ce3fd68649c19adc483ce924" ) ); + BOOST_CHECK( bitcoin_address( p2pkh_testnet ).get_raw_address() == standard_p2pkh_testnet ); + + std::string p2sh_mainnet( "3EktnHQD7RiAE6uzMj2ZifT9YgRrkSgzQX" ); + bytes standard_p2sh_mainnet( parse_hex( "8f55563b9a19f321c211e9b9f38cdf686ea07845" ) ); + BOOST_CHECK( bitcoin_address( p2sh_mainnet ).get_raw_address() == standard_p2sh_mainnet ); + + std::string p2sh_testnet( "2MzQwSSnBHWHqSAqtTVQ6v47XtaisrJa1Vc" ); + bytes standard_p2sh_testnet( parse_hex( "4e9f39ca4688ff102128ea4ccda34105324305b0" ) ); + BOOST_CHECK( bitcoin_address( p2sh_testnet ).get_raw_address() == standard_p2sh_testnet ); +} + +BOOST_AUTO_TEST_CASE( create_multisig_address_test ) +{ + + std::vector public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" ); + std::vector public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" ); + std::vector public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" ); + + std::vector address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" ); + std::vector redeem_script = parse_hex( "522103db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010210320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b441798321033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f53ae" ); + + fc::ecc::public_key_data key1 = create_public_key_data( public_key1 ); + fc::ecc::public_key_data key2 = create_public_key_data( public_key2 ); + fc::ecc::public_key_data key3 = create_public_key_data( public_key3 ); + + sidechain::btc_multisig_address cma(2, { { account_id_type(1), public_key_type(key1) }, { account_id_type(2), public_key_type(key2) }, { account_id_type(3), public_key_type(key3) } }); + + BOOST_CHECK( address == cma.raw_address ); + BOOST_CHECK( redeem_script == cma.redeem_script ); +} + +BOOST_AUTO_TEST_CASE( create_segwit_address_test ) +{ + // https://0bin.net/paste/nfnSf0HcBqBUGDto#7zJMRUhGEBkyh-eASQPEwKfNHgQ4D5KrUJRsk8MTPSa + std::vector public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" ); + std::vector public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" ); + std::vector public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" ); + + std::vector witness_script = parse_hex("0020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1"); + + fc::ecc::public_key_data key1 = create_public_key_data( public_key1 ); + fc::ecc::public_key_data key2 = create_public_key_data( public_key2 ); + fc::ecc::public_key_data key3 = create_public_key_data( public_key3 ); + + sidechain::btc_multisig_segwit_address address(2, { { account_id_type(1), public_key_type(key1) }, { account_id_type(2), public_key_type(key2) }, { account_id_type(3), public_key_type(key3) } }); + BOOST_CHECK( address.get_witness_script() == witness_script ); + BOOST_CHECK( address.get_address() == "2NGU4ogScHEHEpReUzi9RB2ha58KAFnkFyk" ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sidechain_tests/bitcoin_sign_tests.cpp b/tests/sidechain_tests/bitcoin_sign_tests.cpp new file mode 100644 index 000000000..d47908cff --- /dev/null +++ b/tests/sidechain_tests/bitcoin_sign_tests.cpp @@ -0,0 +1,396 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include "../common/database_fixture.hpp" + +using namespace sidechain; +using namespace fc::ecc; + +BOOST_FIXTURE_TEST_SUITE( bitcoin_sign_tests, database_fixture ) + +inline bytes get_privkey_bytes( const std::string& privkey_base58 ) +{ + const auto privkey = fc::from_base58( privkey_base58 ); + // Remove version and hash + return bytes( privkey.cbegin() + 1, privkey.cbegin() + 1 + 32 ); +} + +BOOST_AUTO_TEST_CASE( btc_tx_witness_signature_test ) +{ + bitcoin_transaction tx; + tx.nVersion = 1; + tx.vin.resize( 1 ); + tx.vout.resize( 1 ); + tx.nLockTime = 0; + + tx.vin[0].prevout.hash = fc::sha256( "0a510f49749aaaa2638048132eafea959dd8e47e79332dbcb2a14189870e3145" ); + tx.vin[0].prevout.n = 1; + tx.vin[0].scriptSig = parse_hex( "220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1" ); + tx.vin[0].nSequence = 4294967295; + + tx.vout[0].value = 20000000; + tx.vout[0].scriptPubKey = parse_hex( "76a9143ebc40e411ed3c76f86711507ab952300890397288ac" ); + + const auto privkey_1 = get_privkey_bytes( "cQPUeypiYqp8J8Y8dGXUhvWGPHXTYYs3haryjdquwvMLAabXAnzF" ); + const auto privkey_2 = get_privkey_bytes( "cTG9AXoZjPbUmU2rx4ojeUBm3P88q3ZvRR6YWTUeVJHyke5KbVPM" ); + const auto redeemScript = parse_hex( "522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae"); + uint64_t amount = 20021300; + int32_t hash_type = 1; + + info_for_vin vin; + vin.out.amount = amount; + vin.script = redeemScript; + + tx.vin[0].scriptWitness.push_back( sign_witness_transaction_part( tx, { redeemScript }, { amount }, privkey_1, db.context_sign, hash_type)[0] ); + tx.vin[0].scriptWitness.push_back( sign_witness_transaction_part( tx, { redeemScript }, { amount }, privkey_2, db.context_sign, hash_type)[0] ); + sign_witness_transaction_finalize( tx, { redeemScript } ); + + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0100000000010145310e878941a1b2bc2d33797ee4d89d95eaaf2e13488063a2aa9a74490f510a0100000023220020b6744de4f6ec63cc92f7c220cdefeeb1b1bed2b66c8e5706d80ec247d37e65a1ffffffff01002d3101000000001976a9143ebc40e411ed3c76f86711507ab952300890397288ac0400473044022001dd489a5d4e2fbd8a3ade27177f6b49296ba7695c40dbbe650ea83f106415fd02200b23a0602d8ff1bdf79dee118205fc7e9b40672bf31563e5741feb53fb86388501483045022100f88f040e90cc5dc6c6189d04718376ac19ed996bf9e4a3c29c3718d90ffd27180220761711f16c9e3a44f71aab55cbc0634907a1fa8bb635d971a9a01d368727bea10169522103b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb2103dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba621033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be153ae00000000" ); +} + +BOOST_AUTO_TEST_CASE( verify_sig_test ) +{ + std::string hash( "df074d23cbedea48308aa2161c5b0da3893cc898e143c285ae4d5d787b366f10" ); + bytes vec_hash( parse_hex( hash ) ); + + std::string key1( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ); + bytes vec_key1( parse_hex( key1 ) ); + + std::string sig( "3044022051641c36fc6bc1e7ddd0022259c3f3a8dce0ac7fa4538c8b303c49e14b216b5302204e64c88a7f0279d902ccb338ffd42941ccdbbd7ddf8ba17d1ebf693f1f0b3ed901" ); + bytes vec_sig( parse_hex( sig ) ); + + BOOST_CHECK( verify_sig( vec_sig, vec_key1, vec_hash, db.context_verify ) ); + + std::string key2( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435ca" ); + bytes vec_key2( parse_hex( key2 ) ); + + BOOST_CHECK( !verify_sig( vec_sig, vec_key2, vec_hash, db.context_verify ) ); +} + +BOOST_AUTO_TEST_CASE( get_pubkey_from_redeemScript_test ) +{ + std::string script( "5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae"); + + std::vector keys = {parse_hex( "025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" ), + parse_hex( "02c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf" )}; + + std::vector keys_from_script = get_pubkey_from_redeemScript( parse_hex( script ) ); + + BOOST_CHECK( keys.size() == keys_from_script.size() ); + for( size_t i = 0; i < keys.size(); i++ ) { + BOOST_CHECK( keys[i] == keys_from_script[i] ); + } + + std::string script2( "5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e95bae" ); + + std::vector keys_from_script2 = get_pubkey_from_redeemScript( parse_hex( script2 ) ); + BOOST_CHECK( keys_from_script2.size() == 1 ); + BOOST_CHECK( keys_from_script2[0] == parse_hex( "025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e9" ) ); +} + +BOOST_AUTO_TEST_CASE( sort_sig_test ) +{ + bitcoin_transaction trx( fc::json::from_string( "{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\",\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\",\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\",\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\",\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}" ).as< bitcoin_transaction >() ); + std::vector scripts( fc::json::from_string( "[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]" ).as< std::vector >() ); + std::vector amounts( fc::json::from_string( "[5993981520,1200000000,1200000000,1200000000]" ).as< std::vector >() ); + std::vector> results( fc::json::from_string( "[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]" ).as< std::vector> >() ); + + auto new_stacks = sort_sigs( trx, scripts, amounts, db.context_verify ); + for( size_t i = 0; i < trx.vin.size(); i++ ) { + BOOST_CHECK( new_stacks[i] == results[i] ); + } +} + +BOOST_AUTO_TEST_CASE( already_sorted_sigs_test ) +{ + bitcoin_transaction trx( fc::json::from_string( "{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e937fd2942f0f14dd46a122e138d00cfabd93572b4876da77ab57c2a76ee73af\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"]},{\"prevout\":{\"hash\":\"ae34ad50ab112e6cc51e6e3a87c48798b67255f8c8a8af9d427cbf55207ecfd1\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"]},{\"prevout\":{\"hash\":\"e04ee70b6aa2180caa32aaa4ff00c80b62e5572c369e05986d3a0e0b6d9d7455\",\"n\":0},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"]},{\"prevout\":{\"hash\":\"6bc22ed725ba7c164df3a878113a11e4fbc3d1bbffee0083e75cb14e7bb5bd38\",\"n\":1},\"scriptSig\":\"220020d85971e91d6e46473104e3f7e5eb67d885304a08dd17b3e1a0eeebe5a15f54a6\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]}],\"vout\":[{\"value\":\"9590365272\",\"scriptPubKey\":\"00206a19177b8e4d76408c574118681f204c1a7065040636d5288af41f67c25a85f0\"},{\"value\":240000,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":240000,\"scriptPubKey\":\"2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471bac\"},{\"value\":240000,\"scriptPubKey\":\"2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c80ac\"},{\"value\":240000,\"scriptPubKey\":\"21021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d99390091ac\"},{\"value\":240000,\"scriptPubKey\":\"2103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b0ac\"},{\"value\":240000,\"scriptPubKey\":\"21028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfaac\"},{\"value\":240000,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":240000,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":240000,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":240000,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":240000,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}" ).as< bitcoin_transaction >() ); + std::vector scripts( fc::json::from_string( "[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102ec74848d166af51b430f6d130606896e1436688e935dd8407c3aa15c38d4471b2102cd19bf004e5d533de24bcc55d8573fe5fada438860512a9bbe37118733b34c8021021d2559f259df45f16287d8f55ab41c1c2fb7099a75cc99a3f250611d993900912103e4857a5da1e9483ba14644421489790120555baccb9cf130848e0261464bb7b021028ba26c831fb21084c9bbb7059f76debbf442822adc286ee43ea3092fd666bcfa21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]" ).as< std::vector >() ); + std::vector amounts( fc::json::from_string( "[5993981520,1200000000,1200000000,1200000000]" ).as< std::vector >() ); + std::vector> results( fc::json::from_string( "[[\"30440220098d274e3de29da36577f88ff851d030051b417a0309b61ba1c90d1750eee432022013946f9434893e4d9cbc23b68ec8243ed935823948d701f007edb6ccc46ac29801\",\"3045022100c2cb782558909109d5971ff29e23011b8eb4cd99e86030ac81a15b3312d897530220690080ce113caf84373ac04cc16e7f62ee5eeaf47b26d8017d2509c5d5510c0201\",\"3045022100888c5c9b5d2a4f3a17713cf665c2c65f3b9e954c2bcf3506deb9e410a33f6ca50220604f2a37e3650aded4c5811826098f8fa18af62fe07273c138440c16bdae074401\",\"3045022100cd4e8db4154b100077a30063654c4fc8473c3856064264293b931968fff9cee9022028ab2c8694218853756cca3e5ef5857694037e56e559a0004b292c7122ab355401\",\"30440220772bd2e8afe8c39d28e0c08ea81281d14239033cb93c92edf52250da542fa7c2022059ca93f98b194c9bcc8017ba3de19893fad06c8ea74430dc5fbe7eb81844598d01\"],[\"3044022010a60381cdb91d1f45579cc1d06df44b57c5af98b475089c8b349ad96a9d84fd02200840cff73d4053521dc4e7b210d20114ca82926ae96eb74633284f03f9d9861c01\",\"304402205639d8b13a6d912a3fd086abd34eb7455320aeb6b7ff148452a469f90fe636c80220035aad331677b67590845a5c6e9f8a6805d7add98cbab7047362a91717934e0001\",\"304402202945a632fe13b14099c80eb29fe6144597ca33b6fe10995a4b8756725149b5d902202b2e6a2b7bb39c7441877feaa1be68d144859ac755099704bd49eac41e12c92e01\",\"304402200e4fd2d3001736fbdabc65d50a3a04b6f99a80dc7a50b7257d65d7ced844c2320220613d8704833c50c445f56769b27067ca68299f8a462e2b62fb9e55d7c3e7046701\",\"3045022100b93623da6ed9a3f75082dbd77fab5492e64ae96ad4cdbb70f5a9ff1b2b30aa2602205df026319f3f21ba6f69f0be2469155c62dcf54ddfaf5f7d489d969b8364a3e401\"],[\"30450221009f1c1053f45450a9e20c7735b645eb3825587ecd9dcb39a0d6de35926dbb252802204bad14928faacca9481d69960d5add5acbf7072e5230a146a7ecb6d9193b7d5001\",\"3045022100f32581419b4b46b3aa3bab0ad80202ba559fa1b086b6b02d003a2aac19782d6b02204e3132c43a12411e52f0d8b3714cb77d82136752a1e939af87d865ccda75b0ec01\",\"3045022100e145ab07653d0b2d472ebc393b5e82eee725c65573dfb458c36c7717aba5994002201cd00d40dca3120db7b38239da801765f042e248785c8d44ce4a0b8b1d57b36901\",\"3045022100f7f48205bdbb5e1690e635bf02205e4790c57aadb3f65adabd4890eb4285cfd5022056a0013f35f59c73dc2048697e0330a439e7b35804b655dbda938bdfc77ced9301\",\"30440220762212bfd15454036502ecd635314f7f81be982ea16dddf892693815745b32c7022069846f5b22b0246737396834123439556c9f8cd640006ad1ef8c70d86ca70a3e01\"],[\"304402202c1bcbd436f95e42364122f9f552466122597050962524850434bfeb0b1a721e02200f5f7cfc4d7c43c550a59918d43ee52e76e04c8da381303558f4fc83cc64e19201\",\"304402201669a5580624132b2f1e8d2a51831816846c5f93505623dc03ea6a9f01f023ed022054c69ae28483cd40ac144b7d4af4ff29292813cda425373eabd8d14624c61aae01\",\"3045022100f1b787c0466e88bbc663df7f5584dfa68416c106ab806e0b9f959c6f51b7221b022042633dfc95cde470690a52d5cc468e2f9a745fc1db2fee692b8f31cfce28c0cb01\",\"304402206468ea767ad5aa2fbe837c29ae2fee4f87063d25b9e97fc6f7f679a036a892bf022055e78030476a78d8fc9177bf2e64ccece65005493a8fc6bd1352741153e7eea601\",\"3045022100c8a830255c4ea9ca205126701fc435d39993eca2d7024817958beea76ad3785102201cb27c7613031a4f55bc3c43683aa57f04a4f73291ad9c1076c5281bc49dc4d101\"]]" ).as< std::vector> >() ); + + auto new_stacks = sort_sigs( trx, scripts, amounts, db.context_verify ); + for( size_t i = 0; i < trx.vin.size(); i++ ) { + BOOST_CHECK( new_stacks[i] == results[i] ); + } +} + +BOOST_AUTO_TEST_CASE( all_signatures_are_same_test ) +{ + bitcoin_transaction trx( fc::json::from_string( "{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"e35635fce1cbe1e347a5f8a3c5dccd79db9d43a6fb4b27991894a8bce1a70e03\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"]},{\"prevout\":{\"hash\":\"1826f1ba0ed5034806cf1cb3eaa5dc9abf04a319048ae1e73e3df75dcc9f0bca\",\"n\":1},\"scriptSig\":\"2200203b9e077c0043e8f394a273baffc0aed01d10d8c894ad39810257d63be9a315e0\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]}],\"vout\":[{\"value\":1997997990,\"scriptPubKey\":\"0020a40e801531fdca0fb550013a9140aaaf8d9eb106c427258fbc86d0e0c52c432d\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"}],\"nLockTime\":0}" ).as< bitcoin_transaction >() ); + std::vector scripts( fc::json::from_string( "[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf5bae\"]" ).as< std::vector >() ); + std::vector amounts( fc::json::from_string( "[998998995,1000000000]" ).as< std::vector >() ); + std::vector> results( fc::json::from_string( "[[\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\",\"304402205370c8999e097e4018b04fa3be9c27e2ff16f0c21ef363c35dfd45b4290bf0740220775506660ece404703801a3f5a13fe24c96821c7d7eb42448abe35a1035cd8c801\"],[\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\",\"3045022100ced739a6c04cf3c5e5bc760272bb6f41ecb3fa3671aa78ac1bc47629e39bb7ba02207a8693778d3b5a0c045fddc1ab23fcd971640460f150252b39993587151ff27d01\"]]" ).as< std::vector> >() ); + + auto new_stacks = sort_sigs( trx, scripts, amounts, db.context_verify ); + for( size_t i = 0; i < trx.vin.size(); i++ ) { + BOOST_CHECK( new_stacks[i] == results[i] ); + } +} + +BOOST_AUTO_TEST_CASE( same_signatures_test ) +{ + bitcoin_transaction trx( fc::json::from_string( "{\"nVersion\":1,\"vin\":[{\"prevout\":{\"hash\":\"f8d70d29817a78e160c53dcffcf6a783008f1cc43c64f7efb49e4de3a23ef016\",\"n\":0},\"scriptSig\":\"\",\"nSequence\":4294967295,\"scriptWitness\":[\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"]},{\"prevout\":{\"hash\":\"dd54a8c470be54b02cd937a1758761863c2faf920b2645d2dd35bd0308ef0dfb\",\"n\":1},\"scriptSig\":\"220020f38dc1aecea9e28bea4410e6aa807be49cf6472b9a718750080b9703e80d9fe9\",\"nSequence\":4294967295,\"scriptWitness\":[\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\"]}],\"vout\":[{\"value\":2996996985,\"scriptPubKey\":\"0020a4d938999fff18a140d830009f8c9a2c5ab00d61cc3ffea10ee703b7d9b24b9a\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cfac\"},{\"value\":66667,\"scriptPubKey\":\"21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcdeac\"},{\"value\":66667,\"scriptPubKey\":\"2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de51ac\"},{\"value\":66667,\"scriptPubKey\":\"2103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6ac\"},{\"value\":66667,\"scriptPubKey\":\"210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d29ac\"},{\"value\":66667,\"scriptPubKey\":\"2103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232fac\"}],\"nLockTime\":0}" ).as< bitcoin_transaction >() ); + std::vector scripts( fc::json::from_string( "[\"552102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d292103c6206bca3492f93b27a877362ffc25a57177fe0be4a7aaad661daead7703232f5bae\",\"5521025feceb66ffc86f38d952786c6d696c79c2dbc239dd4e91b46729d73a27fb57e92102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf2102c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf21030d9f1f4be73391d5814bb00cdb6ae10b4a1182a32a77672b5b744efa2e88dcde2102096d78d32f51a1051c8e4f58ea99427ce335e76f6ea00c915cf6d7ac1270de512103b0184a0323802226dfaa767d0bc93e261762d3ae4457c04ca2613215365d2dc6210226d279da5bfd81f7ab9ab804a3d0b44a06dd883a1f29d4671d4da4793b9d0d295bae\"]" ).as< std::vector >() ); + std::vector amounts( fc::json::from_string( "[1997997990,1000000000]" ).as< std::vector >() ); + std::vector> results( fc::json::from_string( "[[\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\",\"304402204cc6d437f1f46263c36bc0605686b6072f0fb7c5991690e8ea06e8126f06a77f02205cfaa0f2e05ab9187fde9b10fbfee3ad06ae8b7120d455277186d1f8fd31b16c01\"\"30440220228c930388a0420aa9a17acdf414763bec0f57f92ecf8db51f02e3f8d82428aa0220417f2d0fbc5fd00d5d158a2e7fe7188857119b8d16d11f82f513594a28dbcbfa01\",\"3045022100d95008906e848a8165fbc0d3ed6d11643bc4814d4f8ae84c8c84a97c8c14885002206a2d703ffcca22309b843b55b746994fb08c15fbbf0aadbf900398e8768c62dc01\"],[\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100c4d233c9183d91fd9f1821fb68e1180bbd6493eb66caf36438bccd8cb46a247302200d3a16ff3180fb9ffe8dd16c8ea71f461e7b940baea4c302c8d8e2ba9bf74b9201\",\"3045022100bdc4d1151d0567bb4e377b473100eaf41544bb547bc6d82b0a0dae8e8e833a6d022013caa911c553558abe6fdf1a6853ca6bab6912d90676595cfd4d11afd4f7966301\",\"3045022100e1262b0e14df0f6f99d850651caa6b8881f7cbf811ad549cb0d6a1b1369beec902204232af72b6bfcb21a83d555374dc622275b7c2cac31b4f56d76b87d88fdc586e01\"]]" ).as< std::vector> >() ); + + auto new_stacks = sort_sigs( trx, scripts, amounts, db.context_verify ); + for( size_t i = 0; i < trx.vin.size(); i++ ) { + BOOST_CHECK( new_stacks[i] == results[i] ); + } +} + +class bitcoin_transaction_sign_evaluator_test : public bitcoin_transaction_sign_evaluator +{ + +public: + + bitcoin_transaction_sign_evaluator_test( transaction_evaluation_state& state ) + { + trx_state = &state; + + private_keys = generate_priv_keys( 15 ); + public_keys = get_public_keys( private_keys ); + + keys_map = create_keys_map( public_keys ); + } + + std::vector generate_priv_keys( const size_t& n ) + { + std::vector result; + for( size_t i = 0; i < n; i++ ) { + result.push_back( private_key::regenerate( fc::digest( i ) ) ); + } + return result; + } + + std::vector get_public_keys( const std::vector& keys ) + { + std::vector result; + for( size_t i = 0; i < keys.size(); i++ ) { + result.push_back( keys[i].get_public_key() ); + } + return result; + } + + accounts_keys create_keys_map( const std::vector& public_keys ) + { + accounts_keys keys_map{ { account_id_type(0), public_key_type( public_key_data( public_keys[0] ) ) }, + { account_id_type(1), public_key_type( public_key_data( public_keys[1] ) ) }, + { account_id_type(2), public_key_type( public_key_data( public_keys[2] ) ) }, + { account_id_type(3), public_key_type( public_key_data( public_keys[3] ) ) }, + { account_id_type(4), public_key_type( public_key_data( public_keys[4] ) ) }, + { account_id_type(5), public_key_type( public_key_data( public_keys[5] ) ) }, + { account_id_type(6), public_key_type( public_key_data( public_keys[6] ) ) }, + { account_id_type(7), public_key_type( public_key_data( public_keys[7] ) ) }, + { account_id_type(8), public_key_type( public_key_data( public_keys[8] ) ) }, + { account_id_type(9), public_key_type( public_key_data( public_keys[9] ) ) }, + { account_id_type(10), public_key_type( public_key_data( public_keys[10] ) ) }, + { account_id_type(11), public_key_type( public_key_data( public_keys[11] ) ) }, + { account_id_type(12), public_key_type( public_key_data( public_keys[12] ) ) }, + { account_id_type(13), public_key_type( public_key_data( public_keys[13] ) ) } }; + return keys_map; + } + + std::vector private_keys; + std::vector public_keys; + + accounts_keys keys_map; +}; + +std::vector create_info_for_vins( const std::vector& addresses ) +{ + std::vector result; + for( size_t i = 0; i < addresses.size(); i++ ) { + info_for_vin vin; + vin.out.hash_tx = "1111111111111111111111111111111111111111111111111111111111111111"; + vin.out.n_vout = static_cast( i ); + vin.out.amount = static_cast( i ); + vin.address = addresses[i].get_address(); + vin.script = addresses[i].get_witness_script(); + result.push_back( vin ); + } + return result; +} + +void sign_transaction( bitcoin_transaction& tx, const private_key& priv_key, const std::vector& redeem_scripts, + const std::vector& amounts, secp256k1_context_t* context_sign ) +{ + const auto secret = priv_key.get_secret(); + bytes key( secret.data(), secret.data() + secret.data_size() ); + auto sigs = sign_witness_transaction_part( tx, redeem_scripts, amounts, key, context_sign, 1 ); + for( size_t j = 0; j < tx.vin.size(); j++ ) { + tx.vin[j].scriptWitness.push_back( sigs[j] ); + } +} + +std::vector create_addresses( database& db, accounts_keys keys_map ) +{ + std::vector addresses; + for( size_t i = 0; i < 5; i++ ) { + const auto& address = db.create( [&]( bitcoin_address_object& a ) { + const private_key petra_private_key = private_key::regenerate( fc::sha256::hash( std::to_string( i ) ) ); + (--keys_map.end())->second = public_key_type( petra_private_key.get_public_key() ); + a.address = sidechain::btc_multisig_segwit_address( 5, keys_map ); + }); + addresses.push_back( address.address ); + } + return addresses; +} + +BOOST_AUTO_TEST_CASE( check_sigs_normal_sigs_test ) +{ + transaction_evaluation_state trx_eval( &db ); + bitcoin_transaction_sign_evaluator_test sign_eval( trx_eval ); + + auto addresses = create_addresses( db, sign_eval.keys_map ); + + std::vector info_for_vins = create_info_for_vins( addresses ); + sidechain_condensing_tx ct( info_for_vins, std::vector() ); + bitcoin_transaction transaction = ct.get_transaction(); + + std::vector redeem_scripts( db.i_w_info.get_redeem_scripts( info_for_vins ) ); + std::vector amounts( db.i_w_info.get_amounts( info_for_vins ) ); + + for( size_t i = 0; i < 4; i++ ) { + sign_transaction( transaction, sign_eval.private_keys[i], redeem_scripts, amounts, db.context_sign ); + } + + const auto secret = sign_eval.private_keys[4].get_secret(); + bytes key( secret.data(), secret.data() + secret.data_size() ); + auto sigs = sign_witness_transaction_part( transaction, redeem_scripts, amounts, key, db.context_sign, 1 ); + + const auto pub_key = sign_eval.keys_map[account_id_type(4)].key_data; + bytes key_hex( public_key_data_to_bytes( pub_key ) ); + BOOST_CHECK( sign_eval.check_sigs( key_hex, sigs, info_for_vins, transaction ) ); +} + +BOOST_AUTO_TEST_CASE( check_sigs_extra_signature_test ) +{ + transaction_evaluation_state trx_eval( &db ); + bitcoin_transaction_sign_evaluator_test sign_eval( trx_eval ); + + const auto& addresses = create_addresses( db, sign_eval.keys_map ); + + std::vector info_for_vins = create_info_for_vins( addresses ); + sidechain_condensing_tx ct( info_for_vins, std::vector() ); + bitcoin_transaction transaction = ct.get_transaction(); + + std::vector redeem_scripts( db.i_w_info.get_redeem_scripts( info_for_vins ) ); + std::vector amounts( db.i_w_info.get_amounts( info_for_vins ) ); + + for( size_t i = 0; i < 5; i++ ) { + sign_transaction( transaction, sign_eval.private_keys[i], redeem_scripts, amounts, db.context_sign ); + } + + const auto secret = sign_eval.private_keys[5].get_secret(); + bytes key(secret.data(), secret.data() + secret.data_size()); + auto sigs = sign_witness_transaction_part( transaction, redeem_scripts, amounts, key, db.context_sign, 1 ); + + const auto pub_key = sign_eval.keys_map[account_id_type(5)].key_data; + bytes key_hex( public_key_data_to_bytes( pub_key ) ); + BOOST_CHECK( !sign_eval.check_sigs( key_hex, sigs, info_for_vins, transaction ) ); +} + +BOOST_AUTO_TEST_CASE( check_sigs_sign_not_match_key_test ) +{ + transaction_evaluation_state trx_eval( &db ); + bitcoin_transaction_sign_evaluator_test sign_eval( trx_eval ); + + const auto& addresses = create_addresses( db, sign_eval.keys_map ); + + std::vector info_for_vins = create_info_for_vins( addresses ); + sidechain_condensing_tx ct( info_for_vins, std::vector() ); + bitcoin_transaction transaction = ct.get_transaction(); + + const auto secret = sign_eval.private_keys[3].get_secret(); + bytes key(secret.data(), secret.data() + secret.data_size()); + + std::vector redeem_scripts( db.i_w_info.get_redeem_scripts( info_for_vins ) ); + std::vector amounts( db.i_w_info.get_amounts( info_for_vins ) ); + + auto sigs = sign_witness_transaction_part( transaction, redeem_scripts, amounts, key, db.context_sign, 1 ); + + const auto pub_key = sign_eval.keys_map[account_id_type(5)].key_data; + bytes key_hex( public_key_data_to_bytes( pub_key ) ); + BOOST_CHECK( !sign_eval.check_sigs( key_hex, sigs, info_for_vins, transaction ) ); +} + +BOOST_AUTO_TEST_CASE( check_sigs_identical_keys_normal_tests ) +{ + transaction_evaluation_state trx_eval( &db ); + bitcoin_transaction_sign_evaluator_test sign_eval( trx_eval ); + + sign_eval.keys_map[account_id_type( 4 )] = sign_eval.keys_map[account_id_type( 3 )]; + + const auto& addresses = create_addresses( db, sign_eval.keys_map ); + + std::vector info_for_vins = create_info_for_vins( addresses ); + sidechain_condensing_tx ct( info_for_vins, std::vector() ); + bitcoin_transaction transaction = ct.get_transaction(); + + std::vector redeem_scripts( db.i_w_info.get_redeem_scripts( info_for_vins ) ); + std::vector amounts( db.i_w_info.get_amounts( info_for_vins ) ); + + for( size_t i = 0; i < 4; i++ ) { + sign_transaction( transaction, sign_eval.private_keys[i], redeem_scripts, amounts, db.context_sign ); + } + + const auto secret = sign_eval.private_keys[3].get_secret(); + bytes key(secret.data(), secret.data() + secret.data_size()); + auto sigs = sign_witness_transaction_part( transaction, redeem_scripts, amounts, key, db.context_sign, 1 ); + + const auto pub_key = sign_eval.keys_map[account_id_type(3)].key_data; + bytes key_hex( public_key_data_to_bytes( pub_key ) ); + BOOST_CHECK( sign_eval.check_sigs( key_hex, sigs, info_for_vins, transaction ) ); +} + +BOOST_AUTO_TEST_CASE( check_sigs_identical_keys_not_normal_tests ) +{ + transaction_evaluation_state trx_eval( &db ); + bitcoin_transaction_sign_evaluator_test sign_eval( trx_eval ); + + sign_eval.keys_map[account_id_type( 4 )] = sign_eval.keys_map[account_id_type( 3 )]; + + const auto& addresses = create_addresses( db, sign_eval.keys_map ); + + std::vector info_for_vins = create_info_for_vins( addresses ); + sidechain_condensing_tx ct( info_for_vins, std::vector() ); + bitcoin_transaction transaction = ct.get_transaction(); + + std::vector redeem_scripts( db.i_w_info.get_redeem_scripts( info_for_vins ) ); + std::vector amounts( db.i_w_info.get_amounts( info_for_vins ) ); + + for( size_t i = 0; i < 4; i++ ) { + sign_transaction( transaction, sign_eval.private_keys[i], redeem_scripts, amounts, db.context_sign ); + } + + const auto secret = sign_eval.private_keys[3].get_secret(); + bytes key(secret.data(), secret.data() + secret.data_size()); + auto sigs = sign_witness_transaction_part( transaction, redeem_scripts, amounts, key, db.context_sign, 1 ); + for( size_t j = 0; j < transaction.vin.size(); j++ ) { + transaction.vin[j].scriptWitness.push_back( sigs[j] ); + } + + const auto pub_key = sign_eval.keys_map[account_id_type(3)].key_data; + bytes key_hex( public_key_data_to_bytes( pub_key ) ); + BOOST_CHECK( !sign_eval.check_sigs( key_hex, sigs, info_for_vins, transaction ) ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sidechain_tests/bitcoin_transaction_tests.cpp b/tests/sidechain_tests/bitcoin_transaction_tests.cpp new file mode 100644 index 000000000..d96e18474 --- /dev/null +++ b/tests/sidechain_tests/bitcoin_transaction_tests.cpp @@ -0,0 +1,169 @@ +#include +#include +#include +#include + +using namespace sidechain; + +BOOST_AUTO_TEST_SUITE( bitcoin_transaction_tests ) + +BOOST_AUTO_TEST_CASE( serialize_bitcoin_transaction_test ) +{ + out_point prevout; + prevout.hash = fc::sha256( "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" ); + prevout.n = 0; + + tx_in in; + in.prevout = prevout; + in.scriptSig = parse_hex( "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" ); + in.nSequence = 4294967294; + + tx_out out1; + out1.value = 3500000000; + out1.scriptPubKey = parse_hex( "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" ); + + tx_out out2; + out2.value = 1499996160; + out2.scriptPubKey = parse_hex( "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" ); + + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin = { in }; + tx.vout = { out1, out2 }; + tx.nLockTime = 101; + + const auto serialized = pack( tx ); + + const auto expected = parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" ); + + BOOST_CHECK_EQUAL_COLLECTIONS( serialized.cbegin(), serialized.cend(), expected.cbegin(), expected.cend() ); +} + +BOOST_AUTO_TEST_CASE( deserialize_bitcoin_transaction_test ) +{ + bitcoin_transaction tx = unpack(parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" ) ); + + BOOST_CHECK_EQUAL( tx.nVersion, 2 ); + BOOST_CHECK_EQUAL( tx.nLockTime, 101 ); + + BOOST_REQUIRE_EQUAL( tx.vin.size(), 1 ); + BOOST_CHECK_EQUAL( tx.vin[0].prevout.hash.str(), "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" ); + BOOST_CHECK_EQUAL( tx.vin[0].prevout.n, 0 ); + + BOOST_CHECK( fc::to_hex( tx.vin[0].scriptSig) == "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" ); + BOOST_CHECK_EQUAL( tx.vin[0].nSequence, 4294967294 ); + + BOOST_REQUIRE_EQUAL( tx.vout.size(), 2 ); + BOOST_CHECK_EQUAL( tx.vout[0].value, 3500000000 ); + BOOST_CHECK( fc::to_hex( tx.vout[0].scriptPubKey ) == "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" ); + BOOST_CHECK_EQUAL(tx.vout[1].value, 1499996160); + BOOST_CHECK( fc::to_hex( tx.vout[1].scriptPubKey ) == "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" ); +} + +BOOST_AUTO_TEST_CASE( btc_tx_methods_test ) { + const auto tx = unpack( parse_hex( "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f856040000002322002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1ffffffff0188b3f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02473044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc012321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac00000000" ) ); + + BOOST_CHECK( tx.get_hash().str() == "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1" ); + BOOST_CHECK( tx.get_txid().str() == "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88" ); + BOOST_CHECK( tx.get_vsize() == 148 ); +} + +BOOST_AUTO_TEST_CASE( bitcoin_transaction_builder_test ) +{ + // All tests are only to verefy the compilation of transactions, the transactions are not real(not valid) + { // P2PKH to P2PKH + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2PKH, fc::sha256( "5d42b45d5a3ddcf2421b208885871121551acf6ea5cc1c1b4e666537ab6fcbef" ), 0, bytes() ); + tb.add_out( payment_type::P2PKH, 4999990000, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex(pack( tx ) ) == "0200000001efcb6fab3765664e1b1ccca56ecf1a552111878588201b42f2dc3d5a5db4425d0000000000ffffffff01f0ca052a010000001976a9143307bf6f98832e53a48b144d65c6a95700a93ffb88ac00000000"); + } + { // coinbase to P2PK + const auto pubkey = fc::raw::unpack( parse_hex( "02028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00" ) ); + out_point prevout; + prevout.hash = fc::sha256( "0000000000000000000000000000000000000000000000000000000000000000" ); + prevout.n = 0xffffffff; + + tx_in txin; + txin.prevout = prevout; + + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, txin, parse_hex( "022a020101" ) ); + tb.add_out( payment_type::P2PK, 625000000, pubkey); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "21030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022a020101ffffffff0240be402500000000232102028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00ac0000000000000000256a2321030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac00000000" ); + } + { // P2SH to P2SH + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, fc::sha256( "40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8" ), 0, bytes() ); + tb.add_out( payment_type::P2SH, 0xffffffff, "3P14159f73E4gFr7JterCCQh9QjiTjiZrG" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000001c8cc2b56525e734ff63a13bc6ad06a9e5664df8c67632253a8e36017aee3ee400000000000ffffffff01ffffffff0000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a8700000000" ); + } + { // P2PK to NULLDATA + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2PK, fc::sha256( "fa897a4a2b8bc507db6cf4425e81ca7ebde89a369e07d608ac7f7c311cb13b4f" ), 0, bytes() ); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "ffffffff" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000014f3bb11c317c7fac08d6079e369ae8bd7eca815e42f46cdb07c58b2b4a7a89fa0000000000ffffffff010000000000000000066a04ffffffff00000000" ); + } + { // P2PK+P2PKH to P2PKH,P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2PK, fc::sha256( "13d29149e08b6ca63263f3dddd303b32f5aab646ebc6b7db84756d80a227f6d9" ), 0, bytes() ); + tb.add_in( payment_type::P2PKH, fc::sha256( "a8e7f661925cdd2c0e37fc93c03540c113aa6bcea02b35de09377127f76d0da3" ), 0, bytes() ); + tb.add_out( payment_type::P2PKH, 4999990000, "mzg9RZ1p29uNXu4uTWoMdMERdVXZpunJhW" ); + tb.add_out( payment_type::P2PKH, 4999990000, "n2SPW6abRxUnnTSSHp73VGahbPW4WT9GaK" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000002d9f627a2806d7584dbb7c6eb46b6aaf5323b30ddddf36332a66c8be04991d2130000000000ffffffffa30d6df727713709de352ba0ce6baa13c14035c093fc370e2cdd5c9261f6e7a80000000000ffffffff02f0ca052a010000001976a914d2276c7ed7af07f697175cc2cbcbbf32da81caba88acf0ca052a010000001976a914e57d9a9af070998bedce991c4d8e39f9c51eb93a88ac00000000" ); + } + { // P2WPKH to P2WPKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 1, bytes() ); + tb.add_out( payment_type::P2WPKH, 99988480, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001600143307bf6f98832e53a48b144d65c6a95700a93ffb00000000" ); + } + { // P2WSH to P2WSH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2WSH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 2, bytes() ); + tb.add_out( payment_type::P2WSH, 99988360, "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560200000000ffffffff0188b3f505000000001600140000010966776006953d5567439e5e39f86a0d2700000000" ); + } + { // P2SH(WPKH) to P2SH(WPKH) + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH_WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 3, parse_hex( "ab68025513c3dbd2f7b92a94e0581f5d50f654e7" ) ); + tb.add_out( payment_type::P2SH_WPKH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560300000014ab68025513c3dbd2f7b92a94e0581f5d50f654e7ffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" ); + } + { // P2SH(WSH) to P2SH(WSH) + bitcoin_transaction_builder tb; + tb.set_version(2); + const auto redeem_script = parse_hex( "21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac"); + tb.add_in( payment_type::P2SH_WSH, fc::sha256( "fca01bd539623013f6f945dc6173c395394621ffaa53a9eb6da6e9a2e7c9400e" ), 0, redeem_script ); + tb.add_out( payment_type::P2SH_WSH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010e40c9e7a2e9a66deba953aaff21463995c37361dc45f9f613306239d51ba0fc000000002321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acacffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" ); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sidechain_tests/db_sidechain_tests.cpp b/tests/sidechain_tests/db_sidechain_tests.cpp new file mode 100644 index 000000000..55b7953c8 --- /dev/null +++ b/tests/sidechain_tests/db_sidechain_tests.cpp @@ -0,0 +1,311 @@ +#include +#include "../common/database_fixture.hpp" + +#include + +#include +#include +#include +#include + +using namespace graphene::chain; +using namespace sidechain; + +template< class SidechainOp > +proposal_object create_op_and_prop( database& db, bool check_size = true ) +{ + const auto& sidechain_proposal_idx = db.get_index_type().indices().get(); + + BOOST_CHECK( !check_size || sidechain_proposal_idx.size() == 0 ); + + const flat_set& active_witnesses = db.get_global_properties().active_witnesses; + const witness_id_type& witness_id = *active_witnesses.begin(); + const witness_object witness = witness_id(db); + const account_object& witness_account = witness.witness_account(db); + + const auto& propose = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + obj.proposed_transaction.operations.push_back( SidechainOp() ); + } ); + + const auto& spropose = db.create( [&]( sidechain_proposal_object& obj ){ + obj.proposal_id = propose.id; + } ); + + return propose; +} + +template< class SidechainOp > +void check_fork_prop( database& db ) +{ + db.modify( db.get_global_properties(), [&]( global_property_object& gpo ) { + gpo.parameters.extensions.value.sidechain_parameters.reset(); + if( gpo.pending_parameters ) + gpo.pending_parameters->extensions.value.sidechain_parameters.reset(); + }); + + BOOST_CHECK( db.is_sidechain_fork_needed() ); + + transaction_evaluation_state context(&db); + + proposal_create_operation proposal_op; + proposal_op.fee_paying_account = account_id_type(); + proposal_op.proposed_ops.emplace_back( SidechainOp() ); + proposal_op.expiration_time = db.head_block_time() + fc::days(1); + + BOOST_CHECK_THROW( db.apply_operation( context, proposal_op ), fc::exception ); +} + +/////////////////////////////////// functions for roll_back tests + +inline proposal_object create_proposals( database& db, bitcoin_transaction_send_operation op ) +{ + const auto& propose_send = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + obj.proposed_transaction.operations.push_back( op ); + } ); + db.create( [&]( sidechain_proposal_object& obj ){ + obj.proposal_id = propose_send.id; + obj.proposal_type = sidechain_proposal_type::SEND_BTC_TRANSACTION; + } ); + + return propose_send; +} + +inline bitcoin_transaction_send_operation create_btc_tx_send_op( const test_environment& env, uint32_t offset = 0 ) +{ + bitcoin_transaction_send_operation op; + op.pw_vin = env.pw_vin; + op.vins = std::vector( env.vins.begin(), env.vins.end() - offset ); + for( const auto& vout : std::vector( env.vouts.begin(), env.vouts.end() - offset ) ) { + op.vouts.push_back( vout.get_id() ); + } + op.transaction = env.full_tx.first; + op.fee_for_size = env.full_tx.second; + return op; +} + +inline void mark_vin( database& db, const test_environment& env ) +{ + for( auto& iter: env.vins ) { + db.i_w_info.mark_as_used_vin( iter ); + } +} + +inline void mark_vout( database& db, const test_environment& env ) +{ + for( auto& iter: env.vouts ) { + db.i_w_info.mark_as_used_vout( iter ); + } +} + +inline void mark_vin_vout_pw( database& db, const test_environment& env ) +{ + mark_vin( db, env ); + mark_vout( db, env ); + db.pw_vout_manager.mark_as_used_vout( env.pw_vin.identifier ); +} + +/////////////////////////////////// + +BOOST_FIXTURE_TEST_SUITE( db_sidechain_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( remove_sidechain_proposals_test ) +{ + const auto& sidechain_proposal_idx = db.get_index_type().indices().get(); + + BOOST_CHECK_EQUAL( sidechain_proposal_idx.size(), 0 ); + + const auto& propose_send = create_op_and_prop( db, false ); + const auto& propose_issue = create_op_and_prop( db, false ); + const auto& propose_revert = create_op_and_prop( db, false ); + + create_op_and_prop( db, false ); + create_op_and_prop( db, false ); + create_op_and_prop( db, false ); + + BOOST_CHECK_EQUAL( sidechain_proposal_idx.size(), 6 ); + + db.remove_sidechain_proposal_object( propose_send ); + db.remove_sidechain_proposal_object( propose_issue ); + db.remove_sidechain_proposal_object( propose_revert ); + + BOOST_CHECK_EQUAL( sidechain_proposal_idx.size(), 3 ); + + const auto& propose_transfer = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + obj.proposed_transaction.operations.push_back( transfer_operation() ); + } ); + + db.remove_sidechain_proposal_object( propose_transfer ); + + BOOST_CHECK_EQUAL( sidechain_proposal_idx.size(), 3 ); +} + +BOOST_AUTO_TEST_CASE( sidechain_fork_btc_issue_op_test ) +{ + check_fork_prop ( db ); +} + +BOOST_AUTO_TEST_CASE( sidechain_fork_btc_send_op_test ) +{ + check_fork_prop ( db ); +} + +BOOST_AUTO_TEST_CASE( sidechain_fork_btc_revert_op_test ) +{ + check_fork_prop ( db ); +} + +BOOST_AUTO_TEST_CASE( roll_back_vin_test ) +{ + test_environment env( db ); + + mark_vin( db, env ); + + bitcoin_transaction_send_operation op = create_btc_tx_send_op( env ); + const auto& propose_send = create_proposals( db, op ); + + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 1 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 0 ); + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + + db.roll_back_vin_and_vout( propose_send ); + + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 5 ); + + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( roll_back_vout_test ) +{ + test_environment env( db ); + + mark_vout( db, env ); + + bitcoin_transaction_send_operation op = create_btc_tx_send_op( env ); + const auto& propose_send = create_proposals( db, op ); + + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 1 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vouts().size(), 0 ); + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + + db.roll_back_vin_and_vout( propose_send ); + + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vouts().size(), 5 ); + + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( roll_back_vin_and_vout_test ) +{ + test_environment env( db ); + + mark_vin_vout_pw( db, env ); + bitcoin_transaction_send_operation op = create_btc_tx_send_op( env ); + const auto& propose_send = create_proposals( db, op ); + + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 1 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 0 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vouts().size(), 0 ); + BOOST_CHECK( db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + db.roll_back_vin_and_vout( propose_send ); + + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 5 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vouts().size(), 5 ); + + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( roll_back_part_test ) +{ + test_environment env( db ); + + mark_vin_vout_pw( db, env ); + bitcoin_transaction_send_operation op = create_btc_tx_send_op( env, 2 ); + + const auto& propose_send = create_proposals( db, op ); + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 1 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 0 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vouts().size(), 0 ); + BOOST_CHECK( db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + db.roll_back_vin_and_vout( propose_send ); + + BOOST_REQUIRE_EQUAL( db.i_w_info.get_info_for_vins().size(), 3 ); + BOOST_REQUIRE_EQUAL( db.i_w_info.get_info_for_vouts().size(), 3 ); + + BOOST_CHECK( !db.pw_vout_manager.get_vout( op.pw_vin.identifier )->used ); + BOOST_CHECK_EQUAL( db.get_index_type().indices().get().size(), 0 ); + + auto info_vins = db.i_w_info.get_info_for_vins(); + auto info_vouts = db.i_w_info.get_info_for_vouts(); + + BOOST_REQUIRE_EQUAL( op.vins.size(), info_vins.size() ); + BOOST_REQUIRE_EQUAL( op.vouts.size(), info_vouts.size() ); + + // no match for `operator==`; but `operator!=` exists + for( uint32_t indx = 0; indx < op.vins.size(); indx++ ) { + BOOST_CHECK( !( op.vins[indx] != info_vins[indx] ) ); + } + + for( uint32_t indx = 0; indx < info_vouts.size(); indx++ ) { + BOOST_CHECK( env.vouts[indx] == info_vouts[indx] ); + } +} + +BOOST_AUTO_TEST_CASE( not_enough_amount_for_fee ) +{ + test_environment env( db ); + + auto vins = env.vins; + for( auto& vin: vins ) { + db.i_w_info.modify_info_for_vin( vin, [&]( info_for_vin& obj ) { + obj.out.amount = 1; + obj.identifier = fc::sha256::hash( obj.out.hash_tx + std::to_string( obj.out.n_vout ) ); + } ); + vin.out.amount = 1; + } + + auto tx_and_new_infos = db.create_tx_with_valid_vin( env.vouts, env.pw_vin ); + + BOOST_CHECK_EQUAL( tx_and_new_infos.second.size(), 0 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 0 ); +} + +BOOST_AUTO_TEST_CASE( not_enough_amount_for_fee_tree_of_five ) +{ + test_environment env( db ); + + auto vins = env.vins; + + for( uint32_t i = 0; i < 3; ++i ) { + db.i_w_info.modify_info_for_vin( vins[i], [&]( info_for_vin& obj ) { + obj.out.amount = 100 + i; + obj.identifier = fc::sha256::hash( obj.out.hash_tx + std::to_string( obj.out.n_vout ) ); + } ); + vins[i].out.amount = 100 + i; + } + + auto tx_and_new_infos = db.create_tx_with_valid_vin( env.vouts, env.pw_vin ); + + BOOST_CHECK_EQUAL( tx_and_new_infos.second.size(), 2 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 2 ); +} + +BOOST_AUTO_TEST_CASE( enough_amount_for_fee ) +{ + test_environment env( db ); + + auto tx_and_new_infos = db.create_tx_with_valid_vin( env.vouts, env.pw_vin ); + + BOOST_CHECK_EQUAL( tx_and_new_infos.second.size(), 5 ); + BOOST_CHECK_EQUAL( db.i_w_info.get_info_for_vins().size(), 5 ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/sidechain_tests/input_withdrawal_info_tests.cpp b/tests/sidechain_tests/input_withdrawal_info_tests.cpp new file mode 100644 index 000000000..c658e5427 --- /dev/null +++ b/tests/sidechain_tests/input_withdrawal_info_tests.cpp @@ -0,0 +1,274 @@ +#include +#include +#include "../common/database_fixture.hpp" +#include +#include + +using namespace graphene::chain; +using namespace sidechain; + +BOOST_FIXTURE_TEST_SUITE( input_withdrawal_info_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_insert_vin_test ) +{ + input_withdrawal_info infos( db ); + prev_out out = { "1", 1, 13 }; + infos.insert_info_for_vin( out, "addr1", { 0x01, 0x02, 0x03 } ); + BOOST_CHECK( infos.size_info_for_vins() == 1 ); + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_many_insert_vin_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 1; i <= 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } ); + } + BOOST_CHECK( infos.size_info_for_vins() == 10 ); + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_id_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } ); + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + BOOST_CHECK( infos.find_info_for_vin( identifier ).valid() ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->id == i ); + } + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_check_data_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } ); + } + + for( size_t i = 0; i < 10; i++ ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + BOOST_CHECK( infos.find_info_for_vin( identifier ).valid() ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->id == i ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.hash_tx == std::to_string( i ) ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.n_vout == i ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.amount == i ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->address == "addr" + std::to_string( i ) ); + std::vector script = { 0x01, 0x02, 0x03 }; + BOOST_CHECK( infos.find_info_for_vin( identifier )->script == script ); + } + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_modify_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } ); + } + + for( size_t i = 0; i < 10; i++ ) { + if( i % 2 == 0 ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + auto iter = infos.find_info_for_vin( identifier ); + BOOST_CHECK( iter.valid() ); + infos.modify_info_for_vin( *iter, [&]( info_for_vin& obj ) { + obj.out.hash_tx = std::to_string( i + 1 ); + obj.out.n_vout = i + 1; + obj.out.amount = i + 1; + obj.address = "addr" + std::to_string( i ) + std::to_string( i ); + } ); + } + } + + for( size_t i = 0; i < 10; i++ ) { + if( i % 2 == 0 ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + BOOST_CHECK( infos.find_info_for_vin( identifier ).valid() ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->id == i ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.hash_tx == std::to_string( i + 1 ) ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.n_vout == i + 1 ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->out.amount == i + 1 ); + BOOST_CHECK( infos.find_info_for_vin( identifier )->address == "addr" + std::to_string( i ) + std::to_string( i ) ); + std::vector script = { 0x01, 0x02, 0x03 }; + BOOST_CHECK( infos.find_info_for_vin( identifier )->script == script ); + } + } + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vin_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 } ); + } + + for( size_t i = 0; i < 10; i++ ) { + if( i % 2 == 0 ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + auto iter = infos.find_info_for_vin( identifier ); + BOOST_CHECK( iter.valid() ); + infos.remove_info_for_vin( *iter ); + } + } + + for( size_t i = 0; i < 10; i++ ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + if( i % 2 == 0 ) { + BOOST_CHECK( !infos.find_info_for_vin( identifier ).valid() ); + } else { + BOOST_CHECK( infos.find_info_for_vin( identifier ).valid() ); + } + } + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vins_test ) +{ + input_withdrawal_info infos( db ); + + const auto pw_obj = db.get_latest_PW(); + auto witnesses_keys = pw_obj.address.witnesses_keys; + + for( size_t i = 0; i < 10; i++ ) { + const auto& address = db.create( [&]( bitcoin_address_object& a ) { + const fc::ecc::private_key petra_private_key = generate_private_key( std::to_string( i ) ); + witnesses_keys.begin()->second = public_key_type( petra_private_key.get_public_key() ); + a.address = sidechain::btc_multisig_segwit_address( 5, witnesses_keys); + }); + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, address.get_address(), { 0x01, 0x02, 0x03 } ); + } + + const auto& vins = infos.get_info_for_vins(); + BOOST_CHECK( vins.size() == 5 ); // 5 amount vins to bitcoin transaction + + for( size_t i = 0; i < 7; i++ ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + auto iter = infos.find_info_for_vin( identifier ); + infos.mark_as_used_vin( *iter ); + } + + const auto& vins2 = infos.get_info_for_vins(); + BOOST_CHECK( vins2.size() == 3 ); + + info_for_vin::count_id_info_for_vin = 0; +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_insert_vout_test ) +{ + input_withdrawal_info infos( db ); + infos.insert_info_for_vout( account_id_type(), "1", 1 ); + BOOST_CHECK( infos.size_info_for_vouts() == 1 ); +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_many_insert_vout_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 1; i <= 10; i++ ) { + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); + } + BOOST_CHECK( infos.size_info_for_vouts() == 10 ); +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_remove_vout_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); + } + + for( size_t i = 0; i < 10; i++ ) { + if( i % 2 == 0 ) { + auto iter = infos.find_info_for_vout( graphene::chain::info_for_vout_id_type(i) ); + BOOST_CHECK( iter.valid() ); + infos.remove_info_for_vout( *iter ); + } + } + + for( size_t i = 0; i < 10; i++ ) { + if( i % 2 == 0 ) { + BOOST_CHECK( !infos.find_info_for_vout( graphene::chain::info_for_vout_id_type(i) ).valid() ); + } else { + BOOST_CHECK( infos.find_info_for_vout( graphene::chain::info_for_vout_id_type(i) ).valid() ); + } + } +} + +BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vouts_test ) +{ + input_withdrawal_info infos( db ); + for( size_t i = 0; i < 10; i++ ) { + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); + } + + const auto& vouts = infos.get_info_for_vouts(); + BOOST_CHECK( vouts.size() == 5 ); // 5 amount vouts to bitcoin transaction + + for( size_t i = 0; i < 7; i++ ) { + auto iter = infos.find_info_for_vout( graphene::chain::info_for_vout_id_type(i) ); + infos.mark_as_used_vout( *iter ); + } + + const auto& vouts2 = infos.get_info_for_vouts(); + BOOST_CHECK( vouts2.size() == 3 ); +} + +BOOST_AUTO_TEST_CASE( info_for_vin_comparer_tests ) +{ + input_withdrawal_info infos( db ); + + auto vins_checker = [&]( std::vector vins_id ) { + auto vins = infos.get_info_for_vins(); + auto i = 0; + for( uint32_t j : vins_id ) { + BOOST_CHECK( vins[i].out.hash_tx == std::to_string( j ) ); + BOOST_CHECK( vins[i].out.n_vout == j ); + BOOST_CHECK( vins[i].out.amount == j ); + BOOST_CHECK( vins[i].address == "addr" + std::to_string( j ) ); + i++; + } + }; + + for( size_t i = 0; i < 10; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + bool resend = ( i % 2 == 0 ) ? true : false; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 }, resend ); + } + vins_checker( { 0, 2, 4, 6, 8 } ); + + for( size_t i = 0; i < 5; i++ ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + auto iter = infos.find_info_for_vin( identifier ); + infos.mark_as_used_vin( *iter ); + } + vins_checker( { 6, 8, 5, 7, 9 } ); + + for( auto i : { 6, 8 } ) { + auto identifier = fc::sha256::hash( std::to_string( i ) + std::to_string( i ) ); + auto iter = infos.find_info_for_vin( identifier ); + infos.mark_as_used_vin( *iter ); + } + vins_checker( { 5, 7, 9 } ); + + for( size_t i = 10; i < 14; i++ ) { + prev_out out = { std::to_string( i ), static_cast( i ), static_cast< uint64_t >( i ) }; + infos.insert_info_for_vin( out, "addr" + std::to_string( i ), { 0x01, 0x02, 0x03 }, true ); + } + vins_checker( { 10, 11, 12, 13, 5 } ); +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sidechain_tests/main.cpp b/tests/sidechain_tests/main.cpp new file mode 100644 index 000000000..6c2174b04 --- /dev/null +++ b/tests/sidechain_tests/main.cpp @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2015 Cryptonomex, Inc., and contributors. + * + * The MIT License + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +#include +#include +#include + +extern uint32_t GRAPHENE_TESTING_GENESIS_TIMESTAMP; + +boost::unit_test::test_suite* init_unit_test_suite(int argc, char* argv[]) { + std::srand(time(NULL)); + std::cout << "Random number generator seeded to " << time(NULL) << std::endl; + const char* genesis_timestamp_str = getenv("GRAPHENE_TESTING_GENESIS_TIMESTAMP"); + if( genesis_timestamp_str != nullptr ) + { + GRAPHENE_TESTING_GENESIS_TIMESTAMP = std::stoul( genesis_timestamp_str ); + } else { + GRAPHENE_TESTING_GENESIS_TIMESTAMP = 1550000001; + } + std::cout << "GRAPHENE_TESTING_GENESIS_TIMESTAMP is " << GRAPHENE_TESTING_GENESIS_TIMESTAMP << std::endl; + return nullptr; +} diff --git a/tests/sidechain_tests/primary_wallet_vout_manager_tests.cpp b/tests/sidechain_tests/primary_wallet_vout_manager_tests.cpp new file mode 100644 index 000000000..19f7f4aee --- /dev/null +++ b/tests/sidechain_tests/primary_wallet_vout_manager_tests.cpp @@ -0,0 +1,158 @@ +#include +#include +#include +#include "../common/database_fixture.hpp" +#include + +using namespace graphene::chain; +using namespace sidechain; + +BOOST_FIXTURE_TEST_SUITE( primary_wallet_vout_manager_tests, database_fixture ) + +void create_primary_wallet_vouts( primary_wallet_vout_manager& pw_vout_manager, const graphene::chain::database& db, uint32_t num ) +{ + uint64_t start_pos = 0; + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + + if( idx.begin() != idx.end() ){ + auto last_itr = --idx.end(); + start_pos = last_itr->id.instance() + 1; + } + + for( uint64_t i = start_pos; i < start_pos + num; i++ ) { + prev_out out = { std::to_string(i), i, i }; + pw_vout_manager.create_new_vout( out ); + } +} + +fc::sha256 create_hash_id( const std::string& hash_tx, const uint32_t& n_vout, const uint64_t& id ) +{ + std::stringstream ss; + ss << std::hex << id; + std::string id_hex = std::string( 16 - ss.str().size(), '0' ) + ss.str(); + + std::string hash_str = fc::sha256::hash( hash_tx + std::to_string( n_vout ) ).str(); + std::string final_hash_id = std::string( hash_str.begin(), hash_str.begin() + 48 ) + id_hex; + + return fc::sha256( final_hash_id ); +} + +BOOST_AUTO_TEST_CASE( check_max_pw_vout_objects ) +{ + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + + primary_wallet_vout_manager pw_vout_manager( db ); + create_primary_wallet_vouts( pw_vout_manager, db, 1 ); + BOOST_CHECK( idx.size() == 2 ); + + create_primary_wallet_vouts( pw_vout_manager, db, 25 ); + BOOST_CHECK( idx.size() == 27 ); + BOOST_CHECK( pw_vout_manager.is_max_vouts() == true ); +} + +BOOST_AUTO_TEST_CASE( check_pw_vout_objects_chain ) +{ + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + primary_wallet_vout_manager pw_vout_manager( db ); + create_primary_wallet_vouts( pw_vout_manager, db, 24 ); + + for( auto itr: idx ) { + BOOST_CHECK( itr.hash_id == create_hash_id( itr.vout.hash_tx, itr.vout.n_vout, itr.id.instance() ) ); + } + + BOOST_CHECK( pw_vout_manager.get_latest_unused_vout().valid() ); + + auto pw_vout = *pw_vout_manager.get_latest_unused_vout(); + + BOOST_CHECK_EQUAL( pw_vout.vout.hash_tx, "24" ); + BOOST_CHECK_EQUAL( pw_vout.vout.n_vout, 24 ); + BOOST_CHECK( pw_vout.hash_id == create_hash_id( "24", 24, pw_vout.id.instance() ) ); +} + +BOOST_AUTO_TEST_CASE( delete_pw_vout_objects ) +{ + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + primary_wallet_vout_manager pw_vout_manager( db ); + create_primary_wallet_vouts( pw_vout_manager, db, 24 ); + + BOOST_CHECK_EQUAL( idx.size(), 25 ); + + auto pw_vout = *pw_vout_manager.get_latest_unused_vout(); + pw_vout_manager.delete_vouts_after( create_hash_id( pw_vout.vout.hash_tx, pw_vout.vout.n_vout, pw_vout.id.instance() ) ); + BOOST_CHECK_EQUAL( idx.size(), 25 ); + + pw_vout_manager.delete_vouts_after( create_hash_id( "13", 13, 13 ) ); + + BOOST_CHECK_EQUAL( idx.size(), 14 ); + + for( auto itr: idx ) { + BOOST_CHECK( itr.hash_id == create_hash_id( itr.vout.hash_tx, itr.vout.n_vout, itr.id.instance() ) ); + } + + create_primary_wallet_vouts( pw_vout_manager, db, 8 ); + pw_vout_manager.delete_vouts_after( create_hash_id( "20", 20, 20 ) ); + BOOST_CHECK_EQUAL( idx.size(), 22 ); + + for( auto itr: idx ) { + BOOST_CHECK( itr.hash_id == create_hash_id( itr.vout.hash_tx, itr.vout.n_vout, itr.id.instance() ) ); + } + + auto itr_primary_wallet = idx.begin(); + pw_vout_manager.delete_vouts_after( create_hash_id( itr_primary_wallet->vout.hash_tx, itr_primary_wallet->vout.n_vout, itr_primary_wallet->id.instance() ) ); + BOOST_CHECK_EQUAL( idx.size(), 1 ); +} + +BOOST_AUTO_TEST_CASE( confirm_pw_vout_objects ) +{ + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + primary_wallet_vout_manager pw_vout_manager( db ); + create_primary_wallet_vouts( pw_vout_manager, db, 1 ); + + auto itr = idx.begin(); + pw_vout_manager.confirm_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ); + + BOOST_CHECK( itr->confirmed == true ); + + itr++; + pw_vout_manager.confirm_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ); + + BOOST_CHECK( itr->confirmed == true ); + BOOST_CHECK_EQUAL( idx.size(), 1 ); + + create_primary_wallet_vouts( pw_vout_manager, db, 1 ); + itr++; + pw_vout_manager.confirm_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ); + + BOOST_CHECK( itr->confirmed == true ); + BOOST_CHECK_EQUAL( idx.size(), 1 ); + + GRAPHENE_REQUIRE_THROW( pw_vout_manager.confirm_vout( fc::sha256::hash( "4" + std::to_string( 4 ))), fc::exception ); + GRAPHENE_REQUIRE_THROW( pw_vout_manager.confirm_vout( fc::sha256::hash( "123" + std::to_string( 123 ))), fc::exception ); +} + +BOOST_AUTO_TEST_CASE( use_pw_vout_objects ) +{ + const auto& idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + primary_wallet_vout_manager pw_vout_manager( db ); + + auto itr = idx.begin(); + pw_vout_manager.mark_as_used_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ); + + BOOST_CHECK( !pw_vout_manager.get_latest_unused_vout().valid() ); + BOOST_CHECK( itr->used == true ); + + create_primary_wallet_vouts( pw_vout_manager, db, 1 ); + itr++; + pw_vout_manager.mark_as_used_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ); + + BOOST_CHECK( !pw_vout_manager.get_latest_unused_vout().valid() ); + BOOST_CHECK( itr->used == true ); + + create_primary_wallet_vouts( pw_vout_manager, db, 2 ); + itr++; + itr++; + + GRAPHENE_REQUIRE_THROW( pw_vout_manager.mark_as_used_vout( create_hash_id( itr->vout.hash_tx, itr->vout.n_vout, itr->id.instance() ) ), fc::exception ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/sidechain_tests/sidechain_condensing_tx_tests.cpp b/tests/sidechain_tests/sidechain_condensing_tx_tests.cpp new file mode 100644 index 000000000..56c1268b1 --- /dev/null +++ b/tests/sidechain_tests/sidechain_condensing_tx_tests.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE( sidechain_condensing_tx_tests ) + +uint64_t size_fee = 100; +uint64_t pw_vout_amount = 113; +double witness_percentage = SIDECHAIN_DEFAULT_PERCENT_PAYMENT_TO_WITNESSES; + +void create_info_vins_and_info_vouts( std::vector& info_vins, std::vector& info_vouts ) +{ + for( size_t i = 0; i < 10; i++ ) { + info_for_vin vin; + vin.out.hash_tx = "1111111111111111111111111111111111111111111111111111111111111111"; + vin.out.n_vout = static_cast( i ); + vin.out.amount = static_cast( i + 10000 ); + vin.address = std::to_string( i ); + vin.script = { 0x0d }; + info_vins.push_back( vin ); + + info_for_vout vout; + vout.payer = account_id_type( i ); + vout.address = sidechain::bitcoin_address( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + vout.amount = static_cast( i + 10000 ); + info_vouts.push_back( vout ); + } +} + +info_for_vin create_pw_vin() +{ + info_for_vin vin_info; + vin_info.out.hash_tx = "2222222222222222222222222222222222222222222222222222222222222222"; + vin_info.out.n_vout = static_cast( 13 ); + vin_info.out.amount = static_cast( 1113 ); + vin_info.address = std::to_string( 13 ); + vin_info.script = { 0x0d }; + + return vin_info; +} + +accounts_keys create_accounts_keys() +{ + fc::ecc::private_key priv_key = fc::ecc::private_key::regenerate( fc::digest( "key" ) ); + return { { account_id_type( 0 ), public_key_type( priv_key.get_public_key() ) }, + { account_id_type( 1 ), public_key_type( priv_key.get_public_key() ) }, + { account_id_type( 2 ), public_key_type( priv_key.get_public_key() ) } }; +} + +BOOST_AUTO_TEST_CASE( create_sidechain_condensing_tx_test ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + const auto& tx = ct.get_transaction(); + + BOOST_CHECK( tx.vin.size() == 10 ); + BOOST_CHECK( tx.vout.size() == 10 ); + for( size_t i = 0; i < 10; i++ ) { + BOOST_CHECK( tx.vin[i].prevout.hash == fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ) ); + BOOST_CHECK( tx.vin[i].prevout.n == static_cast( i ) ); + bytes scriptSig = { 0x22, 0x0d }; + BOOST_CHECK( tx.vin[i].scriptSig == scriptSig ); + BOOST_CHECK( tx.vin[i].scriptWitness == std::vector() ); + + BOOST_CHECK( tx.vout[i].value == static_cast( i + 10000 ) ); + const auto address_bytes = fc::from_base58( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + bytes raw_address( address_bytes.begin() + 1, address_bytes.begin() + 21 ); + bytes scriptPubKey = script_builder() << op::DUP << op::HASH160 << raw_address << op::EQUALVERIFY << op::CHECKSIG; + BOOST_CHECK( tx.vout[i].scriptPubKey == scriptPubKey ); + } +} + +BOOST_AUTO_TEST_CASE( creation_additional_in_out_sidechain_condensing_tx ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( 1113, bytes{ 0x0d, 0x0d, 0x0d } ); + const auto& tx = ct.get_transaction(); + + BOOST_CHECK( tx.vin.size() == 11 ); + BOOST_CHECK( tx.vout.size() == 14 ); + + BOOST_CHECK( tx.vin[0].prevout.hash == fc::sha256( "2222222222222222222222222222222222222222222222222222222222222222" ) ); + BOOST_CHECK( tx.vin[0].prevout.n == static_cast( 13 ) ); + bytes scriptSig = { 0x22, 0x0d }; + BOOST_CHECK( tx.vin[0].scriptSig == bytes() ); + + BOOST_CHECK( tx.vout[0].value == static_cast( 1113 ) ); + bytes scriptPubKey{ 0x00, 0x01, 0x0d }; + BOOST_CHECK( tx.vout[0].scriptPubKey == scriptPubKey ); + for( size_t i = 1; i <= keys.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( 0 ) ); + bytes scriptPubKey{ 0x21 }; + const auto key_data = keys[account_id_type( i - 1 )].key_data; + std::copy( key_data.begin(), key_data.end(), std::back_inserter( scriptPubKey ) ); + scriptPubKey.push_back( 0xac ); + BOOST_CHECK( tx.vout[i].scriptPubKey == scriptPubKey ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + accounts_keys keys = create_accounts_keys(); + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage / GRAPHENE_100_PERCENT ); + } + + uint64_t witnesses_fee_sum = std::accumulate( witnesses_fee.begin(), witnesses_fee.end(), 0 ); + uint64_t witness_fee = witnesses_fee_sum / keys.size(); + BOOST_CHECK( tx.vout[0].value == static_cast( pw_vout_amount - size_fee_user * info_vins.size() ) ); + for( size_t i = 1; i <= keys.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( witness_fee ) ); + } + + size_t offset = 1 + keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_pw_vout_and_witness_vouts_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage / GRAPHENE_100_PERCENT ); + } + + for( size_t i = 0; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i].amount - size_fee_user ) - witnesses_fee[i] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_witness_vouts_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage / GRAPHENE_100_PERCENT ); + } + + size_t offset = 1; + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_pw_vout_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage / GRAPHENE_100_PERCENT ); + } + + size_t offset = keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_vins_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + info_vins.clear(); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage / GRAPHENE_100_PERCENT ); + } + + size_t offset = 1 + keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/sidechain_tests/sidechain_operation_tests.cpp b/tests/sidechain_tests/sidechain_operation_tests.cpp new file mode 100644 index 000000000..ec14fb126 --- /dev/null +++ b/tests/sidechain_tests/sidechain_operation_tests.cpp @@ -0,0 +1,223 @@ +#include +#include "../common/database_fixture.hpp" + +#include + +#include +#include +#include +#include +#include + +using namespace graphene::chain; + +BOOST_FIXTURE_TEST_SUITE( sidechain_operation_tests, database_fixture ) + +void create_bitcoin_issue_operation_environment( database& db ) +{ + std::vector< fc::sha256 > 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::string( 64, std::to_string( i )[0] ) ); + obj.out.amount = 100000 + i * 100000; + obj.out.hash_tx = std::string( 64, std::to_string( i )[0] ); + obj.out.n_vout = 0; + obj.address = std::to_string( i ); + }); + + 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.identifier ); + 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" ); + obj.fee_for_size = 0; + }); +} + +BOOST_AUTO_TEST_CASE( create_bitcoin_address_test ) +{ + transaction_evaluation_state context(&db); + + bitcoin_address_create_operation op; + op.payer = account_id_type(); + op.owner = account_id_type(); + + const auto& idx = db.get_index_type().indices().get< by_id >(); + + BOOST_CHECK( idx.size() == 1 ); + + db.apply_operation( context, op ); + + auto btc_address = idx.begin(); + BOOST_CHECK( btc_address->count_invalid_pub_key == 1 ); +} + +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( std::string( 64,'1' ) ), std::set() ) ); + + BOOST_CHECK( btc_trx_idx.size() == 1 ); + BOOST_CHECK( vins_info_idx.size() == 3 ); + BOOST_CHECK( vouts_info_idx.size() == 3 ); + BOOST_CHECK( db.bitcoin_confirmations.size() == 1 ); + + bitcoin_issue_operation op; + op.payer = db.get_sidechain_account_id(); + op.transaction_ids = { fc::sha256( std::string( 64,'1' ) ) }; + db.apply_operation( context, op ); + + BOOST_CHECK( btc_trx_idx.size() == 0 ); + BOOST_CHECK( vins_info_idx.size() == 0 ); + BOOST_CHECK( vouts_info_idx.size() == 0 ); + BOOST_CHECK( 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + + 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() ) ); + BOOST_CHECK( itr != btc_trx_idx.end() ); + + BOOST_CHECK( 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( std::string( 64,'1' ) ), std::set() ) ); + + { + 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + 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_ids = { fc::sha256( std::string( 64,'1' ) ) }; + GRAPHENE_REQUIRE_THROW( db.apply_operation( context, op ) , fc::exception ); + + session.undo(); + } +} + +BOOST_AUTO_TEST_CASE( bitcoin_transaction_revert_operation_test ) +{ + transaction_evaluation_state context(&db); + const auto& btc_tx_idx = db.get_index_type().indices().get< by_id >(); + const auto& vins_info_idx = db.get_index_type().indices().get< by_identifier >(); + const auto& vouts_info_idx = db.get_index_type().indices().get< by_id >(); + + create_bitcoin_issue_operation_environment( db ); + db.bitcoin_confirmations.insert( sidechain::bitcoin_transaction_confirmations( fc::sha256( std::string( 64, '1' ) ), std::set() ) ); + + auto vin = *vins_info_idx.find( fc::sha256( std::string( 64, '1' ) ) ); + + bitcoin_transaction_revert_operation revert_op; + revert_trx_info info; + info.transaction_id = fc::sha256( std::string( 64, '1' ) ); + info.valid_vins.insert( fc::sha256( std::string( 64, '1' ) ) ); + revert_op.transactions_info.push_back( info ); + + db.apply_operation( context, revert_op ); + + BOOST_CHECK_EQUAL( vins_info_idx.size(), 0 ); + BOOST_CHECK_EQUAL( btc_tx_idx.size(), 0 ); + + auto vin_info = db.i_w_info.find_info_for_vin( fc::sha256::hash( vin.out.hash_tx + std::to_string( vin.out.n_vout ) ) ); + BOOST_CHECK( vin_info.valid() ); +} + +BOOST_AUTO_TEST_SUITE_END() \ No newline at end of file diff --git a/tests/sidechain_tests/sidechain_proposal_checker_tests.cpp b/tests/sidechain_tests/sidechain_proposal_checker_tests.cpp new file mode 100644 index 000000000..efd3ce626 --- /dev/null +++ b/tests/sidechain_tests/sidechain_proposal_checker_tests.cpp @@ -0,0 +1,386 @@ +#include +#include "../common/database_fixture.hpp" +#include + +#include +#include +#include +#include +#include + +using namespace sidechain; + +BOOST_FIXTURE_TEST_SUITE( sidechain_proposal_checker_tests, database_fixture ) + +BOOST_AUTO_TEST_CASE( check_reuse_normal_test ) +{ + sidechain_proposal_checker checker( db ); + bitcoin_transaction_send_operation op; + + op.pw_vin = info_for_vin( { std::string( 64, '1' ), 0, 0 }, "", bytes() ); + for( size_t i = 0; i < 5; i++ ) { + op.vins.push_back( info_for_vin( { std::string( 64, std::to_string( i )[0] ), 0, 0 }, "", bytes() ) ); + op.vouts.push_back( info_for_vout_id_type( i ) ); + } + + BOOST_CHECK( checker.check_reuse( op ) ); +} + +BOOST_AUTO_TEST_CASE( check_reuse_not_normal_test ) +{ + sidechain_proposal_checker checker( db ); + bitcoin_transaction_send_operation op; + + op.pw_vin = info_for_vin( { std::string( 64, '1' ), 0, 0 }, "", bytes() ); + for( size_t i = 0; i < 5; i++ ) { + op.vins.push_back( info_for_vin( { std::string( 64, std::to_string( i )[0] ), 0, 0 }, "", bytes() ) ); + op.vouts.push_back( info_for_vout_id_type( i ) ); + } + + BOOST_CHECK( checker.check_reuse( op ) ); + BOOST_CHECK( !checker.check_reuse( op ) ); +} + +BOOST_AUTO_TEST_CASE( check_btc_tx_send_op_normal_test ) +{ + sidechain_proposal_checker checker( db ); + test_environment env( db ); + + bitcoin_transaction_send_operation op; + op.pw_vin = env.pw_vin; + op.vins = env.vins; + for( const auto& vout : env.vouts ) { + op.vouts.push_back( vout.get_id() ); + } + op.transaction = env.full_tx.first; + op.fee_for_size = env.full_tx.second; + + BOOST_CHECK( checker.check_bitcoin_transaction_send_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( check_btc_tx_send_op_incorrect_info_for_vin_test ) +{ + sidechain_proposal_checker checker( db ); + test_environment env( db ); + + env.vins[3].out.amount = 13; + + bitcoin_transaction_send_operation op; + op.pw_vin = env.pw_vin; + op.vins = env.vins; + for( const auto& vout : env.vouts ) { + op.vouts.push_back( vout.get_id() ); + } + op.transaction = env.full_tx.first; + op.fee_for_size = env.full_tx.second; + + BOOST_CHECK( !checker.check_bitcoin_transaction_send_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( check_btc_tx_send_op_incorrect_info_for_pw_vin_test ) +{ + sidechain_proposal_checker checker( db ); + test_environment env( db ); + + env.pw_vin.out.amount = 13; + + bitcoin_transaction_send_operation op; + op.pw_vin = env.pw_vin; + op.vins = env.vins; + for( const auto& vout : env.vouts ) { + op.vouts.push_back( vout.get_id() ); + } + op.transaction = env.full_tx.first; + op.fee_for_size = env.full_tx.second; + + BOOST_CHECK( !checker.check_bitcoin_transaction_send_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( check_btc_tx_send_op_incorrect_tx_test ) +{ + sidechain_proposal_checker checker( db ); + test_environment env( db ); + + bitcoin_transaction_send_operation op; + op.pw_vin = env.pw_vin; + op.vins = env.vins; + for( const auto& vout : env.vouts ) { + op.vouts.push_back( vout.get_id() ); + } + + env.full_tx.first.vout[0].value = 13; + + op.transaction = env.full_tx.first; + op.fee_for_size = env.full_tx.second; + + BOOST_CHECK( !checker.check_bitcoin_transaction_send_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( twice_approve_btc_tx_send_test ) +{ + sidechain_proposal_checker checker( db ); + const flat_set& active_witnesses = db.get_global_properties().active_witnesses; + const auto& proposal_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + transaction_evaluation_state context(&db); + + auto propose = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + } ); + + const witness_id_type& witness_id = *active_witnesses.begin(); + const witness_object witness = witness_id(db); + const account_object& witness_account = witness.witness_account(db); + + proposal_update_operation op; + { + op.proposal = propose.id; + op.fee_paying_account = witness_account.id; + op.active_approvals_to_add.insert( witness_account.id ); + } + + BOOST_CHECK( proposal_idx.size() == 1 ); + BOOST_CHECK_EQUAL( proposal_idx.begin()->available_active_approvals.size(), 0 ); + BOOST_CHECK( checker.check_witness_opportunity_to_approve( witness, *proposal_idx.begin() ) ); + + db.apply_operation( context, op ); + + BOOST_CHECK( proposal_idx.size() == 1 ); + BOOST_CHECK_EQUAL( proposal_idx.begin()->available_active_approvals.size(), 1 ); + BOOST_CHECK( !checker.check_witness_opportunity_to_approve( witness, *proposal_idx.begin() ) ); +} + +BOOST_AUTO_TEST_CASE( not_active_wit_try_to_approve_btc_send_test ) +{ + ACTOR(nathan); + upgrade_to_lifetime_member( nathan_id ); + trx.clear(); + witness_id_type nathan_witness_id = create_witness( nathan_id, nathan_private_key ).id; + + const auto& witnesses = db.get_global_properties().active_witnesses; + BOOST_CHECK( std::find(witnesses.begin(), witnesses.end(), nathan_witness_id) == witnesses.end() ); + + const auto& witness_idx = db.get_index_type().indices().get(); + auto itr = witness_idx.find( nathan_witness_id ); + BOOST_CHECK( itr != witness_idx.end() ); + + sidechain_proposal_checker checker( db ); + const auto& proposal_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto propose = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + } ); + + BOOST_CHECK( !checker.check_witness_opportunity_to_approve( *itr, *proposal_idx.begin() ) ); +} + +BOOST_AUTO_TEST_CASE( not_account_auths_wit_try_to_approve_btc_send_test ) +{ + ACTOR(nathan); + upgrade_to_lifetime_member(nathan_id); + trx.clear(); + witness_id_type nathan_witness_id = create_witness(nathan_id, nathan_private_key).id; + // Give nathan some voting stake + transfer(committee_account, nathan_id, asset(10000000)); + generate_block(); + test::set_expiration( db, trx ); + + { + account_update_operation op; + op.account = nathan_id; + op.new_options = nathan_id(db).options; + op.new_options->votes.insert(nathan_witness_id(db).vote_id); + op.new_options->num_witness = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(), + [](vote_id_type id) { return id.type() == vote_id_type::witness; }); + op.new_options->num_committee = std::count_if(op.new_options->votes.begin(), op.new_options->votes.end(), + [](vote_id_type id) { return id.type() == vote_id_type::committee; }); + trx.operations.push_back(op); + sign( trx, nathan_private_key ); + PUSH_TX( db, trx ); + trx.clear(); + } + + generate_blocks(db.get_dynamic_global_properties().next_maintenance_time); + const auto& witnesses = db.get_global_properties().active_witnesses; + auto itr_ = std::find(witnesses.begin(), witnesses.end(), nathan_witness_id); + BOOST_CHECK(itr_ != witnesses.end()); + + const auto& witness_idx = db.get_index_type().indices().get(); + auto itr = witness_idx.find( nathan_witness_id ); + BOOST_CHECK( itr != witness_idx.end() ); + + sidechain_proposal_checker checker( db ); + const auto& proposal_idx = db.get_index_type().indices().get< graphene::chain::by_id >(); + auto propose = db.create( [&]( proposal_object& obj ){ + obj.expiration_time = db.head_block_time() + fc::days(1); + obj.review_period_time = db.head_block_time() + fc::days(1); + } ); + + BOOST_CHECK( !checker.check_witness_opportunity_to_approve( *itr, *proposal_idx.begin() ) ); +} + +BOOST_AUTO_TEST_CASE ( check_witness_keys_test_normal ) +{ + sidechain_proposal_checker checker( db ); + + auto witnesses_keys = db.get_latest_PW().address.witnesses_keys; + const auto& bitcoin_address = db.create( [&]( bitcoin_address_object& obj ) { + witnesses_keys.erase( ++witnesses_keys.begin() ); + obj.address = sidechain::btc_multisig_segwit_address( SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG, witnesses_keys ); + obj.owner = account_id_type( 13 ); + } ); + + info_for_vin vin; + vin.address = bitcoin_address.address.address; + + bitcoin_transaction_send_operation op; + op.vins = { vin }; + + proposal_object proposal; + proposal.proposed_transaction.operations.push_back( op ); + + const witness_id_type& witness_id = *db.get_global_properties().active_witnesses.begin(); + const witness_object witness = witness_id( db ); + + BOOST_CHECK( checker.check_witnesses_keys( witness, proposal ) ); +} + +BOOST_AUTO_TEST_CASE ( check_witnesses_keys_incorrect_witness_test ) +{ + sidechain_proposal_checker checker( db ); + + auto witnesses_keys = db.get_latest_PW().address.witnesses_keys; + const auto& bitcoin_address = db.create( [&]( bitcoin_address_object& obj ) { + witnesses_keys.erase( ++witnesses_keys.begin() ); + obj.address = sidechain::btc_multisig_segwit_address( SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG, witnesses_keys ); + obj.owner = account_id_type( 13 ); + } ); + + info_for_vin vin; + vin.address = bitcoin_address.address.address; + + bitcoin_transaction_send_operation op; + op.vins = { vin }; + + proposal_object proposal; + proposal.proposed_transaction.operations.push_back( op ); + + const witness_object witness; + + BOOST_CHECK( !checker.check_witnesses_keys( witness, proposal ) ); +} + +BOOST_AUTO_TEST_CASE ( check_witnesses_keys_nonexistent_witness_test ) +{ + sidechain_proposal_checker checker( db ); + + auto witnesses_keys = db.get_latest_PW().address.witnesses_keys; + const auto& bitcoin_address = db.create( [&]( bitcoin_address_object& obj ) { + witnesses_keys.erase( ++witnesses_keys.begin() ); + obj.address = sidechain::btc_multisig_segwit_address( SIDECHAIN_DEFAULT_NUMBER_SIG_MULTISIG, witnesses_keys ); + obj.owner = account_id_type( 13 ); + } ); + + info_for_vin vin; + vin.address = bitcoin_address.address.address; + + bitcoin_transaction_send_operation op; + op.vins = { vin }; + + proposal_object proposal; + proposal.proposed_transaction.operations.push_back( op ); + + const witness_id_type& witness_id = *++db.get_global_properties().active_witnesses.begin(); + const witness_object witness = witness_id( db ); + + BOOST_CHECK( !checker.check_witnesses_keys( witness, proposal ) ); +} + +void create_missing_bto( graphene::chain::database& db, uint32_t amount ) +{ + BOOST_REQUIRE( amount < 9 ); + while( amount-- > 0 ) { + std::set vins { fc::sha256( std::string( 64,'1' + amount ) ), fc::sha256( std::string( 64,'2' + amount ) ) }; + sidechain::bitcoin_transaction_confirmations btc_trx_conf ( fc::sha256( std::string( 64,'1' + amount ) ), vins ); + btc_trx_conf.missing = true; + db.bitcoin_confirmations.insert( btc_trx_conf ); + + db.create( [&]( graphene::chain::bitcoin_transaction_object& obj ){ + obj.transaction_id = fc::sha256( std::string( 64,'1' + amount ) ); + } ); + } +} + +std::vector< revert_trx_info > get_transactions_info( graphene::chain::database& db, uint32_t amount ) +{ + using iter_by_missing = btc_tx_confirmations_index::index::type::iterator; + std::vector< revert_trx_info > transactions_info; + const auto& btc_trx_idx = db.get_index_type().indices().get(); + BOOST_CHECK_EQUAL( btc_trx_idx.size() , amount ); + + db.bitcoin_confirmations.safe_for([&]( iter_by_missing itr_b, iter_by_missing itr_e ){ + for(auto iter = itr_b; iter != itr_e; iter++) { + if( !iter->is_missing_and_not_used() ) return; + const auto& btc_tx = btc_trx_idx.find( iter->transaction_id ); + if( btc_tx == btc_trx_idx.end() ) continue; + transactions_info.push_back( revert_trx_info( iter->transaction_id, iter->valid_vins ) ); + } + }); + + return transactions_info; +} + +BOOST_AUTO_TEST_CASE( bitcoin_transaction_revert_operation_checker_test ) +{ + using namespace graphene::chain; + const uint32_t amount = 5; + + create_missing_bto( db, amount ); + sidechain_proposal_checker checker( db ); + + bitcoin_transaction_revert_operation op; + + std::vector< revert_trx_info > transactions_info = get_transactions_info( db, amount ); + + op.transactions_info = transactions_info; + + BOOST_CHECK_EQUAL( transactions_info.size(), amount ); + BOOST_CHECK( checker.check_bitcoin_transaction_revert_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( bitcoin_transaction_revert_operation_checker_failed_test ) +{ + using namespace graphene::chain; + const uint32_t amount = 5; + + create_missing_bto( db, amount ); + + std::set vins { fc::sha256( std::string( 64,'1' + 6 ) ), fc::sha256( std::string( 64,'1' + 8 ) ) }; + fc::sha256 trx_id( std::string( 64,'1' + 8 ) ); + sidechain::bitcoin_transaction_confirmations btc_trx_conf ( trx_id, vins ); + db.bitcoin_confirmations.insert( btc_trx_conf ); + db.create( [&]( graphene::chain::bitcoin_transaction_object& obj ){ + obj.transaction_id = fc::sha256( std::string( 64,'1' + 7 ) ); + } ); + + sidechain_proposal_checker checker( db ); + + bitcoin_transaction_revert_operation op; + std::vector< revert_trx_info > transactions_info = get_transactions_info( db, amount + 1 ); + + op.transactions_info = transactions_info; + op.transactions_info.push_back( revert_trx_info( trx_id, vins) ); + + BOOST_CHECK_EQUAL( op.transactions_info.size(), amount + 1 ); + BOOST_CHECK( !checker.check_bitcoin_transaction_revert_operation( op ) ); +} + +BOOST_AUTO_TEST_CASE( no_btc_trx_to_revert_test ) +{ + sidechain_proposal_checker checker( db ); + + bitcoin_transaction_revert_operation op; + BOOST_CHECK( !checker.check_bitcoin_transaction_revert_operation( op ) ); +} + +BOOST_AUTO_TEST_SUITE_END()