Compare commits

...

48 commits

Author SHA1 Message Date
Alexander Suslikov
9b79928a15 Added sidechain hardfork time 2019-03-26 17:27:54 +03:00
Alexander Suslikov
f616e4c755 Deleted idump in subscribe_to_item; added deleting zero-balance object in adjust_balance 2019-03-22 17:23:47 +03:00
Anton Boltach
4a20369013 Fix: do_evaluate method incorrect witness 2019-03-20 10:21:35 +03:00
Anton Shkinder
22826fd680 Added control to small input amount 2019-03-07 10:33:17 +03:00
Anton Shkinder
b747c7bb3c Added Boost version control; Added flag for gcc 6.0 2019-02-25 09:48:23 +03:00
Anzhy Cherrnyavski
d07d3bb9fc Private keys management fixed (cli_wallet) 2019-02-22 16:02:53 +03:00
Anton Shkinder
329c75668f Fixed revert proposal: multiply createion; fix pw vin delete 2019-02-20 16:31:02 +03:00
Anton Shkinder
0109bc8c04 Fix : Segmentation fault when running several witness nodes on the same machine 2019-02-14 16:09:33 +03:00
Anzhy Cherrnyavski
2142bc4d08 Fixed issue-11 2019-02-14 15:46:09 +03:00
Anton Shkinder
113ba26632 Fixed existing tests; add more unit tests for db_sidechain methods 2019-02-13 19:34:07 +03:00
Anton Boltach
b26e3bc8a1 Add public key verification mechanism. 2019-02-13 14:57:33 +03:00
Alexander Suslikov
b27656d2c9 Added is_max_vouts check for PW before bitcoin transaction creatation 2019-02-12 12:33:29 +03:00
Alexander Suslikov
a229d0acc4 Added initializing bitcoin_confirmations when node starts; made correct reindex 2019-02-12 12:15:49 +03:00
Anton Shkinder
b33c5bf86f Fixed thread_safe_index::remove and thread_safe_index::modify 2019-02-11 11:46:15 +03:00
Anton Shkinder
fa2eff74c2 Added check_bitcoin_transaction_revert_operation 2019-02-11 11:00:33 +03:00
Alexander Suslikov
7428c10414 Added resend flag into info_for_vin, changed comparer, made test for comparer 2019-02-08 18:18:41 +03:00
Alexander Suslikov
774e7b34cb Мade executable for sidechain tests and added bitcoin_transaction_revert_operation_test 2019-02-08 16:30:26 +03:00
Anton Shkinder
a9ca86cab2 Added creation of bitcoin revert proposals 2019-02-08 13:19:04 +03:00
Anzhy Cherrnyavski
320cba9f42 Added checking bitcoin inputs 2019-02-08 12:00:49 +03:00
Anton Shkinder
9a5c9092e1 Added approval proposal checker 2019-02-07 17:48:28 +03:00
Alexander Suslikov
7a78d95241 Added bitcoin_transaction_revert_operation with evaluator 2019-02-07 17:06:03 +03:00
Anzhy Cherrnyavski
ec3b7e8b80 Added class sidechain_proposal_checker 2019-02-07 11:49:10 +03:00
Alexander Suslikov
0eda7025c9 Changed constant params to dynamic sidechain_parameters 2019-02-07 11:14:03 +03:00
Alexander Suslikov
7c02821b84 Added reserve_issue and check_amount_higher_than_fee into withdraw_pbtc_evaluator 2019-02-06 15:11:54 +03:00
Alexander Suslikov
f6c2e8c92d Fixed fee bug and bug with info_for_vout 2019-02-06 10:38:29 +03:00
Alexander Suslikov
40e13800f2 Added creation and approving propose with issue 2019-02-06 10:07:07 +03:00
Anzhy Cherrnyavski
f4afdbe145 Changed bitcoin_transaction_send_operation. Fixed bugs. 2019-02-01 16:05:23 +03:00
Alexander Suslikov
e1d99702e4 Added bitcoin_issue_operation, bitcoin_issue_evaluator with tests 2019-02-01 15:49:01 +03:00
Alexander Suslikov
ff57653584 Filled update_tx_approvals in sidechain_net_manager 2019-01-31 15:45:20 +03:00
Anzhy Cherrnyavski
810051e7c9 Signature bitcoin transaction and confirmation proposal 2019-01-31 14:50:31 +03:00
Anzhy Cherrnyavski
38ba47bdd5 Mechanism remove sidechain_proposals 2019-01-31 14:28:35 +03:00
Alexander Suslikov
787a0382be Made thread_safe_index methods with typename, changed input_withdrawal_info 2019-01-29 16:52:40 +03:00
Anzhy Cherrnyavski
ffc51ba1ea Added mechanism create proposal (send_btc_transaction) 2019-01-29 15:02:39 +03:00
Alexander Suslikov
f22d61855b Added sidechain_proposal_object creation, changed thread_safe_index and input_withdrawal_info 2019-01-28 11:18:22 +03:00
Anzhy Cherrnyavski
df264442da Added sign_btc_transaction_operation and sign_btc_transaction_evaluator 2019-01-25 13:46:40 +03:00
Alexander Suslikov
6babaf2c6a Added PW address creation, set sidechain parameters; using PW address for bitcoin_address creation. 2019-01-25 11:44:17 +03:00
Alexander Suslikov
12c5ec3f3f Added finalize_bitcoin_transaction, changed bitcoin_transaction_object and bitcoin_transaction_send_operation 2019-01-24 14:22:17 +03:00
Alexander Suslikov
0bb271a455 Added bitcoin_transaction_create_operation, evaluator; filled sidechain_net_manager::send_btc_tx 2019-01-22 15:43:48 +03:00
Anzhy Cherrnyavski
7a6fca9180 Added mechanism sign bitcoin transaction. 2019-01-21 16:15:12 +03:00
a.suslikov
9c7694a5e4 Added sidechain_proposal_object 2019-01-18 18:08:43 +03:00
Anzhy Cherrnyavski
fd5a650eeb The mechanism for creating a thickening transaction and calculating the commission. 2019-01-18 15:37:22 +03:00
Alexander Suslikov
67486275c1 Added pw_vout_object, pw_vout_manager and tests for pw_vout_manager 2019-01-18 14:13:49 +03:00
Alexander Suslikov
0c42d7ea6f Displaced connection check from zmq_listener to sidechain_net_manager 2019-01-15 12:32:08 +03:00
Anzhy Cherrnyavski
9137f31960 Filling I/O queues. Create class bitcoin_address. 2019-01-15 12:31:45 +03:00
Alexander Suslikov
73106a3b7e Created functional related to creating and getting bitcoin_address_object 2019-01-15 12:17:00 +03:00
Anzhy Cherrnyavski
5ebb987b8e Added bitcoin address (btc_multisig_address and btc_multisig_segwit_address). 2019-01-15 12:01:38 +03:00
Alexander Suslikov
24c6963ef3 Added rpc and zmq modules for comminication with bitcoin node 2019-01-15 12:01:16 +03:00
Anzhy Cherrnyavski
d18e36d459 Information storage for inputs and outputs 2019-01-15 11:55:02 +03:00
97 changed files with 7957 additions and 43 deletions

5
.gitignore vendored
View file

@ -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

View file

@ -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" )

View file

@ -10,3 +10,4 @@ add_subdirectory( utilities )
add_subdirectory( app )
add_subdirectory( plugins )
add_subdirectory( wallet )
add_subdirectory( sidechain )

View file

@ -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<boost::filesystem::path>(), "File to read Genesis State from")
("dbg-init-key", bpo::value<string>(), "Block signing key to use for init witnesses, overrides genesis file")
("api-access", bpo::value<boost::filesystem::path>(), "JSON file specifying API permissions")
("bitcoin-node-ip", bpo::value<string>()->implicit_value("127.0.0.1"), "IP address of Bitcoin node")
("bitcoin-node-zmq-port", bpo::value<uint32_t>()->implicit_value(28332), "ZMQ port of Bitcoin node")
("bitcoin-node-rpc-port", bpo::value<uint32_t>()->implicit_value(18332), "RPC port of Bitcoin node")
("bitcoin-node-rpc-user", bpo::value<string>(), "Bitcoin RPC user")
("bitcoin-node-rpc-password", bpo::value<string>(), "Bitcoin RPC password")
;
command_line_options.add(configuration_file_options);
command_line_options.add_options()

View file

@ -161,6 +161,9 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
vector<tournament_object> get_tournaments_by_state(tournament_id_type stop, unsigned limit, tournament_id_type start, tournament_state state);
vector<tournament_id_type> get_registered_tournaments(account_id_type account_filter, uint32_t limit) const;
//Sidechain
vector<bitcoin_address_object> get_bitcoin_addresses(const account_id_type& acc_id) const;
//private:
template<typename T>
@ -172,7 +175,6 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
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<tournament_id_type> database_api_impl::get_registered_tournaments(account
return tournament_ids;
}
//////////////////////////////////////////////////////////////////////
// //
// Sidechain //
// //
//////////////////////////////////////////////////////////////////////
vector<bitcoin_address_object> database_api::get_bitcoin_addresses(const account_id_type& acc) const
{
return my->get_bitcoin_addresses(acc);
}
vector<bitcoin_address_object> database_api_impl::get_bitcoin_addresses(const account_id_type& acc) const
{
vector<bitcoin_address_object> result;
const auto& btc_addr_idx = _db.get_index_type<bitcoin_address_index>().indices().get<by_owner>();
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 //

View file

@ -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<account_id_type>& result )

View file

@ -47,6 +47,7 @@
#include <graphene/chain/worker_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
@ -644,6 +645,11 @@ class database_api
* @return the list of tournaments that a given account is registered to play in
*/
vector<tournament_id_type> get_registered_tournaments(account_id_type account_filter, uint32_t limit) const;
/**
* @return the list of bitcoin addresses which belong to acc_id
*/
vector<bitcoin_address_object> 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)
)

View file

@ -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" )

View file

@ -0,0 +1,46 @@
#include <graphene/chain/bitcoin_address_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
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<account_index>().indices().get<by_id>();
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>( [&]( 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

View file

@ -0,0 +1,274 @@
#include <graphene/chain/bitcoin_transaction_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <graphene/chain/info_for_used_vin_object.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <sidechain/sign_bitcoin_transaction.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <sidechain/sidechain_proposal_checker.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
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<bitcoin_transaction_send_operation&>( 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<fc::sha256> bitcoin_transaction_send_evaluator::create_transaction_vins( bitcoin_transaction_send_operation& op )
{
database& d = db();
std::vector<fc::sha256> 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<bytes> redeem_scripts( d.i_w_info.get_redeem_scripts( vins ) );
std::vector<uint64_t> amounts( d.i_w_info.get_amounts( vins ) );
std::vector<std::vector<sidechain::bytes>> 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<proposal_index>().indices().get<by_id>();
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<bitcoin_transaction_send_operation>();
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<bitcoin_transaction_send_operation>();
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<bytes>& sigs,
const std::vector<sidechain::info_for_vin>& 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<bitcoin_address_index>().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<int64_t>( 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<bytes> 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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
const auto& vouts_info_idx = d.get_index_type<info_for_vout_index>().indices().get<by_id>();
const auto& vins_info_idx = d.get_index_type<info_for_used_vin_index>().indices().get<by_identifier>();
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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
const auto& vouts_info_idx = d.get_index_type<info_for_vout_index>().indices().get<by_id>();
const auto& vins_info_idx = d.get_index_type<info_for_used_vin_index>().indices().get<by_identifier>();
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<sidechain::by_hash>( trx_info.transaction_id );
d.remove( btc_trx_obj );
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
} }

View file

@ -33,4 +33,5 @@
#include "db_market.cpp"
#include "db_update.cpp"
#include "db_witness_schedule.cpp"
#include "db_notify.cpp"
#include "db_notify.cpp"
#include "db_sidechain.cpp"

View file

@ -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) ) }

View file

@ -176,6 +176,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, [&]()
{
@ -333,6 +336,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 ) {
@ -393,6 +397,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;
//
@ -409,6 +417,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 )
@ -560,6 +589,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 );
@ -573,6 +604,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);

View file

@ -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();
}
} }

View file

@ -49,6 +49,12 @@
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/info_for_vout_object.hpp>
#include <graphene/chain/info_for_used_vin_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <graphene/chain/sport_object.hpp>
@ -76,6 +82,10 @@
#include <graphene/chain/event_evaluator.hpp>
#include <graphene/chain/betting_market_evaluator.hpp>
#include <graphene/chain/tournament_evaluator.hpp>
#include <graphene/chain/withdraw_pbtc_evaluator.hpp>
#include <graphene/chain/bitcoin_address_evaluator.hpp>
#include <graphene/chain/bitcoin_transaction_evaluator.hpp>
#include <graphene/chain/sidechain_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -85,6 +95,7 @@
#include <boost/algorithm/string.hpp>
namespace graphene { namespace chain {
// C++ requires that static class variables declared and initialized
@ -237,6 +248,12 @@ void database::initialize_evaluators()
register_evaluator<tournament_join_evaluator>();
register_evaluator<game_move_evaluator>();
register_evaluator<tournament_leave_evaluator>();
register_evaluator<withdraw_pbtc_evaluator>();
register_evaluator<bitcoin_address_create_evaluator>();
register_evaluator<bitcoin_transaction_send_evaluator>();
register_evaluator<bitcoin_transaction_sign_evaluator>();
register_evaluator<bitcoin_issue_evaluator>();
register_evaluator<bitcoin_transaction_revert_evaluator>();
}
void database::initialize_indexes()
@ -301,6 +318,13 @@ void database::initialize_indexes()
//add_index< primary_index<distributed_dividend_balance_object_index > >();
add_index< primary_index<pending_dividend_payout_balance_for_holder_object_index > >();
add_index< primary_index<total_distributed_dividend_balance_object_index > >();
add_index< primary_index<info_for_vout_index > >();
add_index< primary_index<info_for_used_vin_index > >();
add_index< primary_index<bitcoin_address_index > >();
add_index< primary_index<primary_wallet_vout_index > >();
add_index< primary_index<sidechain_proposal_index > >();
add_index< primary_index<bitcoin_transaction_index > >();
}
void database::init_genesis(const genesis_state_type& genesis_state)

View file

@ -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) ) }

View file

@ -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<account_id_type>& result )

View file

@ -0,0 +1,432 @@
#include <graphene/chain/database.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/protocol/asset.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <sidechain/sidechain_condensing_tx.hpp>
#include <sidechain/sign_bitcoin_transaction.hpp>
#include <sidechain/sidechain_proposal_checker.hpp>
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<witness_index>().indices().get<by_id>();
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>( [&]( account_object& obj ) {
obj.name = "sidechain_account";
obj.statistics = create<account_statistics_object>([&]( 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>( [&]( 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>([&]( 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<witness_index>().indices().get<by_id>();
std::vector<account_id_type> 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>( [&]( 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<bitcoin_address_index>().indices().get<by_owner>();
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<sidechain_proposal_index>().indices().get< by_id >();
const auto& proposal_idx = get_index_type<proposal_index>().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<bitcoin_transaction_send_operation>();
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<bitcoin_transaction_revert_operation>();
if( checker.check_bitcoin_transaction_revert_operation( op ) )
approve_propose( proposal->id );
break;
}
}
}
}
full_btc_transaction database::create_btc_transaction( const std::vector<info_for_vin>& info_vins,
const std::vector<info_for_vout>& 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_for_vin> 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_for_vout>& 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_for_vin> 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<operation> 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<info_for_vin>
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<operation>();
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<operation>();
}
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<proposal_index>().indices().get<by_id>();
const auto& proposal_itr = proposal_idx.find( proposal_id );
bitcoin_transaction_send_operation op = proposal_itr->proposed_transaction.operations.back().get<bitcoin_transaction_send_operation>();
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<bytes> redeem_scripts( i_w_info.get_redeem_scripts( vins ) );
std::vector<uint64_t> 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<bitcoin_transaction_send_operation>::value ||
proposal.proposed_transaction.operations.back().which() == operation::tag<bitcoin_issue_operation>::value ||
proposal.proposed_transaction.operations.back().which() == operation::tag<bitcoin_transaction_revert_operation>::value ) )
{
const auto& sidechain_proposal_idx = get_index_type<sidechain_proposal_index>().indices().get<by_proposal>();
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<bitcoin_transaction_send_operation>::value )
{
bitcoin_transaction_send_operation op = proposal.proposed_transaction.operations.back().get<bitcoin_transaction_send_operation>();
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<operation> database::create_bitcoin_issue_proposals( const witness_object& current_witness )
{
std::vector<fc::sha256> trx_ids;
bitcoin_confirmations.safe_for<by_confirmed_and_not_used>([&]( btc_tx_confirmations_index::index<by_confirmed_and_not_used>::type::iterator itr_b, btc_tx_confirmations_index::index<by_confirmed_and_not_used>::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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
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<operation>( proposal_op );
}
return fc::optional<operation>();
}
fc::optional<operation> database::create_bitcoin_revert_proposals( const witness_object& current_witness )
{
using iter_by_missing = btc_tx_confirmations_index::index<by_missing_and_not_used>::type::iterator;
std::vector<revert_trx_info> trx_info;
bitcoin_confirmations.safe_for<by_missing_and_not_used>([&]( 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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
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<operation>( proposal_op );
}
return fc::optional<operation>();
}
void database::restore_bitcoin_transaction_status()
{
const auto& btc_tx_idx = get_index_type<bitcoin_transaction_index>().indices().get<by_id>();
for( const auto& bto : btc_tx_idx ) {
std::set<fc::sha256> 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 ) );
}
}
} }

View file

@ -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);
}
}

View file

@ -0,0 +1,4 @@
// sidechain fardfork
#ifndef HARDFORK_SIDECHAIN_TIME
#define HARDFORK_SIDECHAIN_TIME (fc::time_point_sec( 1550000000 ))
#endif

View file

@ -0,0 +1,20 @@
#pragma once
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/exceptions.hpp>
namespace graphene { namespace chain {
class bitcoin_address_create_evaluator : public evaluator<bitcoin_address_create_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

View file

@ -0,0 +1,47 @@
#pragma once
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/witness_object.hpp>
#include <sidechain/bitcoin_address.hpp>
namespace graphene { namespace chain {
class bitcoin_address_object : public abstract_object<bitcoin_address_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<by_id>, member< object, object_id_type, &object::id > >,
ordered_unique< tag<by_address>, const_mem_fun< bitcoin_address_object, std::string, &bitcoin_address_object::get_address > >,
ordered_non_unique< tag<by_owner>, member< bitcoin_address_object, account_id_type, &bitcoin_address_object::owner > >
>
> bitcoin_address_multi_index_container;
typedef generic_index<bitcoin_address_object, bitcoin_address_multi_index_container> bitcoin_address_index;
} }
FC_REFLECT_DERIVED( graphene::chain::bitcoin_address_object, (graphene::chain::object), (owner)(address)(count_invalid_pub_key) )

View file

@ -0,0 +1,55 @@
#pragma once
#include <graphene/chain/evaluator.hpp>
namespace sidechain { class info_for_vin; }
namespace graphene { namespace chain {
class bitcoin_transaction_send_evaluator : public evaluator<bitcoin_transaction_send_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<fc::sha256> 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<bitcoin_transaction_sign_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<sidechain::bytes>& sigs,
const std::vector<sidechain::info_for_vin>& info_for_vins, const sidechain::bitcoin_transaction& tx );
};
class bitcoin_transaction_revert_evaluator : public evaluator<bitcoin_transaction_revert_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

View file

@ -0,0 +1,41 @@
#pragma once
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <sidechain/bitcoin_transaction.hpp>
namespace graphene { namespace chain {
class bitcoin_transaction_object : public abstract_object<bitcoin_transaction_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_object, bitcoin_transaction_multi_index_container> 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) )

View file

@ -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 )
//////////////////////////////////////////////////////////////////////

View file

@ -40,9 +40,20 @@
#include <graphene/chain/protocol/protocol.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <sidechain/bitcoin_transaction_confirmations.hpp>
#include <sidechain/primary_wallet_vout_manager.hpp>
#include <atomic>
#include <fc/log/logger.hpp>
#include <map>
#include <secp256k1.h>
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<Types...> helpers);
///@}
///@}
//////////////////// db_sidechain.cpp ////////////////////
public:
using full_btc_tx_and_new_vins = std::pair< sidechain::full_btc_transaction, std::vector<info_for_vin> >;
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_for_vin> 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_for_vout>& info_vouts,
const info_for_vin& info_pw_vin );
sidechain::full_btc_transaction create_btc_transaction( const std::vector<info_for_vin>& info_vins,
const std::vector<info_for_vout>& info_vouts,
const info_for_vin& info_pw_vin );
fc::optional<operation> 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<operation> create_bitcoin_issue_proposals( const witness_object& current_witness );
fc::optional<operation> create_bitcoin_revert_proposals( const witness_object& current_witness );
fc::signal<void( const bitcoin_transaction& )> send_btc_tx;
sidechain::input_withdrawal_info i_w_info;
sidechain::thread_safe_index<sidechain::btc_tx_confirmations_index> bitcoin_confirmations;
sidechain::primary_wallet_vout_manager pw_vout_manager;
std::atomic<uint64_t> 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;

View file

@ -0,0 +1,39 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/generic_index.hpp>
#include <sidechain/types.hpp>
#include <sidechain/bitcoin_address.hpp>
namespace graphene { namespace chain {
class info_for_used_vin_object : public abstract_object<info_for_used_vin_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<tag<by_identifier>, member<info_for_used_vin_object, fc::sha256, &info_for_used_vin_object::identifier>>
>
> info_for_used_vin_multi_index_container;
typedef generic_index<info_for_used_vin_object, info_for_used_vin_multi_index_container> info_for_used_vin_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::info_for_used_vin_object, (graphene::chain::object), (identifier)(out)(address)(script) )

View file

@ -0,0 +1,55 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/generic_index.hpp>
#include <sidechain/types.hpp>
#include <sidechain/bitcoin_address.hpp>
namespace graphene { namespace chain {
class info_for_vout_object : public abstract_object<info_for_vout_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<tag<by_id_and_not_used>, identity< info_for_vout_object >, info_for_vout_object::comparer >
>
> info_for_vout_multi_index_container;
typedef generic_index<info_for_vout_object, info_for_vout_multi_index_container> info_for_vout_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(address)(amount)(used) )

View file

@ -0,0 +1,44 @@
#pragma once
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <sidechain/types.hpp>
namespace graphene { namespace chain {
class primary_wallet_vout_object : public abstract_object<primary_wallet_vout_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_object, primary_wallet_vout_multi_index_container> primary_wallet_vout_index;
} }
FC_REFLECT_DERIVED( graphene::chain::primary_wallet_vout_object, (graphene::chain::object), (vout)(hash_id)(confirmed)(used) )

View file

@ -29,6 +29,25 @@
#include <graphene/chain/transaction_evaluation_state.hpp>
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<typename T>
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<proposal_create_evaluator>
{

View file

@ -0,0 +1,26 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
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) )

View file

@ -0,0 +1,96 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <sidechain/bitcoin_transaction.hpp>
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<sidechain::bytes> 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) )

View file

@ -26,6 +26,7 @@
#include <graphene/chain/protocol/ext.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <fc/smart_ref_fwd.hpp>
#include <sidechain/sidechain_parameters.hpp>
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<bet_multiplier_type, bet_multiplier_type> > permitted_betting_odds_increments;
optional< uint16_t > live_betting_delay_time;
optional< sidechain::sidechain_parameters_extension > sidechain_parameters;
};
struct chain_parameters

View file

@ -44,6 +44,10 @@
#include <graphene/chain/protocol/event.hpp>
#include <graphene/chain/protocol/betting_market.hpp>
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/protocol/withdraw_pbtc.hpp>
#include <graphene/chain/protocol/bitcoin_address.hpp>
#include <graphene/chain/protocol/bitcoin_transaction.hpp>
#include <graphene/chain/protocol/sidechain.hpp>
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

View file

@ -0,0 +1,28 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
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<fc::sha256> 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) )

View file

@ -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 )

View file

@ -0,0 +1,33 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
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) )

View file

@ -0,0 +1,26 @@
#pragma once
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/exceptions.hpp>
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<uint64_t> get_amounts_to_issue( std::vector<fc::sha256> vins_identifier );
std::vector<account_id_type> get_accounts_to_issue( std::vector<fc::sha256> vins_identifier );
};
} } // graphene::chain

View file

@ -0,0 +1,35 @@
#pragma once
#include <graphene/db/generic_index.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <sidechain/types.hpp>
namespace graphene { namespace chain {
class sidechain_proposal_object : public abstract_object<sidechain_proposal_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_object, sidechain_multi_index_container> sidechain_proposal_index;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::sidechain_proposal_object, (graphene::chain::object), (proposal_id)(proposal_type) )

View file

@ -0,0 +1,27 @@
#pragma once
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <sidechain/types.hpp>
using namespace sidechain;
namespace graphene { namespace chain {
class withdraw_pbtc_evaluator : public evaluator<withdraw_pbtc_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

View file

@ -30,6 +30,7 @@
#include <graphene/chain/protocol/tournament.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <fc/smart_ref_impl.hpp>
@ -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>([&]( 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>([&]( 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<sidechain::by_hash>( trx_id );
if( trx_confirmations.valid() ) {
db.bitcoin_confirmations.modify<sidechain::by_hash>( 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>([&]( 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<sidechain::by_hash>( trx.transaction_id );
if( trx_confirmations.valid() ) {
db.bitcoin_confirmations.modify<sidechain::by_hash>( 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<bitcoin_transaction_send_operation>::value ) ||
( o.proposed_ops[0].op.which() == operation::tag<bitcoin_issue_operation>::value ) ||
( o.proposed_ops[0].op.which() == operation::tag<bitcoin_transaction_revert_operation>::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<account_id_type> 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) ) }

View file

@ -0,0 +1,141 @@
#include <graphene/chain/sidechain_evaluator.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <graphene/chain/info_for_used_vin_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
const auto& btc_addr_idx = d.get_index_type<bitcoin_address_index>().indices().get<by_address>();
const auto& vins_info_idx = d.get_index_type<info_for_used_vin_index>().indices().get<by_identifier>();
const auto& vouts_info_idx = d.get_index_type<info_for_vout_index>().indices().get<by_id>();
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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
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<info_for_used_vin_index>().indices().get<by_identifier>();
const auto& vouts_info_idx = d.get_index_type<info_for_vout_index>().indices().get<by_id>();
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<sidechain::by_hash>( btc_obj.transaction_id );
if( trx_approvals.valid() ) {
d.bitcoin_confirmations.remove<sidechain::by_hash>( btc_obj.transaction_id );
}
d.remove( btc_obj );
}
std::vector<uint64_t> bitcoin_issue_evaluator::get_amounts_to_issue( std::vector<fc::sha256> vins_identifier )
{
database& d = db();
const auto& vins_info_idx = d.get_index_type<info_for_used_vin_index>().indices().get<by_identifier>();
std::vector<uint64_t> 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<account_id_type> bitcoin_issue_evaluator::get_accounts_to_issue( std::vector<fc::sha256> vins_identifier )
{
database& d = db();
const auto& btc_addr_idx = d.get_index_type<bitcoin_address_index>().indices().get<by_address>();
const auto& vins_info_idx = d.get_index_type<info_for_used_vin_index>().indices().get<by_identifier>();
std::vector<account_id_type> 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

View file

@ -0,0 +1,85 @@
#include <graphene/chain/withdraw_pbtc_evaluator.hpp>
#include <graphene/chain/info_for_vout_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <sidechain/sidechain_condensing_tx.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <sidechain/bitcoin_address.hpp>
#include <sidechain/utils.hpp>
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

View file

@ -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"

View file

@ -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" )

View file

@ -25,6 +25,7 @@
#include <graphene/app/plugin.hpp>
#include <graphene/chain/database.hpp>
#include <sidechain/network/sidechain_net_manager.hpp>
#include <fc/thread/future.hpp>
@ -82,6 +83,7 @@ private:
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<chain::public_key_type, fc::ecc::private_key> _private_keys;
std::set<chain::witness_id_type> _witnesses;

View file

@ -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<std::string>();
const auto zmq_port = options.at("bitcoin-node-zmq-port").as<uint32_t>();
const auto rpc_port = options.at("bitcoin-node-rpc-port").as<uint32_t>();
const auto rpc_user = options.at("bitcoin-node-rpc-user").as<std::string>();
const auto rpc_password = options.at("bitcoin-node-rpc-password").as<std::string>();
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() }

View file

@ -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" )

View file

@ -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 <sidechain/bech32.hpp>
// #include <bech32.h>
namespace
{
typedef std::vector<uint8_t> 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<std::string, data> 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

View file

@ -0,0 +1,258 @@
#include <sidechain/bitcoin_address.hpp>
#include <sstream>
#include <fc/crypto/base58.hpp>
#include <sidechain/segwit_addr.hpp>
#include <sidechain/bitcoin_script.hpp>
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<unsigned char>( hex_addr[0] ) == 0x00 ||
static_cast<unsigned char>( 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<unsigned char>( hex_addr[0] ) == 0x05 ||
static_cast<unsigned char>( 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<public_key_type> btc_multisig_segwit_address::get_keys() {
std::vector<public_key_type> 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;
}
}

View file

@ -0,0 +1,60 @@
#include <sidechain/bitcoin_script.hpp>
#include <sidechain/serialize.hpp>
namespace sidechain {
script_builder& script_builder::operator<<( op opcode )
{
const auto op_byte = static_cast<uint8_t>( 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<uint8_t>( op::_0 ) );
else
script.push_back( static_cast<uint8_t>( 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;
}
}

View file

@ -0,0 +1,279 @@
#include <sidechain/bitcoin_transaction.hpp>
#include <fc/crypto/base58.hpp>
#include <sidechain/bitcoin_script.hpp>
#include <sidechain/serialize.hpp>
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<char>(0x00) && scriptPubKey[1] == static_cast<char>(0x20) ) {
return true;
}
return false;
}
bool tx_out::is_p2wpkh() const
{
if( scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast<char>(0x00) && scriptPubKey[1] == static_cast<char>(0x14) ) {
return true;
}
return false;
}
bool tx_out::is_p2pkh() const
{
if( scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast<char>(0x76) && scriptPubKey[1] == static_cast<char>(0xa9) &&
scriptPubKey[2] == static_cast<char>(0x14) && scriptPubKey[23] == static_cast<char>(0x88) && scriptPubKey[24] == static_cast<char>(0xac) ) {
return true;
}
return false;
}
bool tx_out::is_p2sh() const
{
if( scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast<char>(0xa9) &&
scriptPubKey[1] == static_cast<char>(0x14) && scriptPubKey[22] == static_cast<char>(0x87) ) {
return true;
}
return false;
}
bool tx_out::is_p2pk() const
{
if( scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast<char>(0x21) && scriptPubKey[34] == static_cast<char>(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<size_t> no_wit_ds;
pack( no_wit_ds, *this, false );
fc::datastream<size_t> 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;
}
}
}

View file

@ -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 <stdint.h>
#include <string>
#include <vector>
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<uint8_t>& values);
/** Decode a Bech32 string. Returns (hrp, data). Empty hrp means failure. */
std::pair<std::string, std::vector<uint8_t>> Decode(const std::string& str);
} } // namespace sidechain::bech32

View file

@ -0,0 +1,133 @@
#pragma once
#include <sidechain/types.hpp>
#include <sidechain/utils.hpp>
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<public_key_type> 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) );

View file

@ -0,0 +1,69 @@
#pragma once
#include <sidechain/types.hpp>
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;
};
}

View file

@ -0,0 +1,100 @@
#pragma once
#include <sidechain/bitcoin_address.hpp>
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<bytes> 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<tx_in> vin;
std::vector<tx_out> 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) )

View file

@ -0,0 +1,61 @@
#pragma once
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <sidechain/thread_safe_index.hpp>
#include <fc/crypto/sha256.hpp>
#include <graphene/chain/protocol/types.hpp>
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<fc::sha256>& 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<fc::sha256> 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<bitcoin_transaction_confirmations,
indexed_by<
ordered_unique<tag<by_hash>, member<bitcoin_transaction_confirmations, fc::sha256, &bitcoin_transaction_confirmations::transaction_id>>,
ordered_non_unique<tag<by_confirmed_and_not_used>, identity< bitcoin_transaction_confirmations >, bitcoin_transaction_confirmations::comparer >,
ordered_non_unique<tag<by_missing_and_not_used>, identity< bitcoin_transaction_confirmations >, bitcoin_transaction_confirmations::comparer >
>
>;
}

View file

@ -0,0 +1,114 @@
#pragma once
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <sidechain/types.hpp>
#include <sidechain/thread_safe_index.hpp>
#include <fc/crypto/sha256.hpp>
#include <graphene/chain/info_for_vout_object.hpp>
#include <graphene/chain/protocol/types.hpp>
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<info_for_vin,
indexed_by<
ordered_unique<tag<by_id>, member<info_for_vin, uint64_t, &info_for_vin::id>>,
ordered_unique<tag<by_identifier>, member<info_for_vin, fc::sha256, &info_for_vin::identifier>>,
ordered_non_unique<tag<by_id_resend_not_used>, identity< info_for_vin >, info_for_vin::comparer >
>
>;
class input_withdrawal_info
{
public:
input_withdrawal_info( graphene::chain::database& _db ) : db( _db ) {}
std::vector<sidechain::bytes> get_redeem_scripts( const std::vector<info_for_vin>& info_vins );
std::vector<uint64_t> get_amounts( const std::vector<info_for_vin>& info_vins );
fc::optional<info_for_vin> 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<void( info_for_vin& e )>& 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<info_for_vin> find_info_for_vin( fc::sha256 identifier );
size_t size_info_for_vins() { return info_for_vins.size(); }
std::vector<info_for_vin> 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<graphene::chain::info_for_vout_object> find_info_for_vout( graphene::chain::info_for_vout_id_type id );
size_t size_info_for_vouts();
std::vector<info_for_vout> get_info_for_vouts();
private:
graphene::chain::database& db;
thread_safe_index<info_for_vin_index> info_for_vins;
};
}
FC_REFLECT( sidechain::info_for_vin, (identifier)(out)(address)(script)(used)(resend) )

View file

@ -0,0 +1,46 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <sidechain/types.hpp>
#include <fc/crypto/sha256.hpp>
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;
};
}

View file

@ -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 <stdint.h>
#include <vector>
#include <string>
namespace sidechain { namespace segwit_addr {
/** Decode a SegWit address. Returns (witver, witprog). witver = -1 means failure. */
std::pair<int, std::vector<uint8_t> > 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<uint8_t>& witprog);
} }

View file

@ -0,0 +1,360 @@
#pragma once
#include <sidechain/types.hpp>
#include <sidechain/bitcoin_transaction.hpp>
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<uint8_t>( size ) );
} else if ( size <= std::numeric_limits<unsigned short>::max() ) {
uint16_t tmp = htole16( static_cast<uint16_t>( size ) );
sb.insert( sb.end(), static_cast<uint8_t>( 253 ) );
sb.insert( sb.end(), reinterpret_cast<const char*>( tmp ), reinterpret_cast<const char*>( tmp ) + sizeof( tmp ) );
} else if ( size <= std::numeric_limits<unsigned int>::max() ) {
uint32_t tmp = htole32( static_cast<uint32_t>( size ) );
sb.insert( sb.end(), static_cast<uint8_t>( 254 ) );
sb.insert( sb.end(), reinterpret_cast<const char*>( tmp ), reinterpret_cast<const char*>( tmp ) + sizeof( tmp ) );
} else {
uint64_t tmp = htole64( static_cast<uint64_t>( size ) );
sb.insert( sb.end(), static_cast<uint8_t>( 255 ) );
sb.insert( sb.end(), reinterpret_cast<const char*>( tmp ), reinterpret_cast<const char*>( tmp ) + sizeof( tmp ) );
}
vec.insert( vec.end(), sb.begin(), sb.end() );
}
template<typename Stream>
inline void pack_compact_size(Stream& s, size_t size)
{
if ( size < 253 ) {
fc::raw::pack( s, static_cast<uint8_t>( size ) );
} else if ( size <= std::numeric_limits<unsigned short>::max() ) {
fc::raw::pack( s, static_cast<uint8_t>( 253 ) );
fc::raw::pack( s, htole16( static_cast<uint16_t>( size ) ) );
} else if ( size <= std::numeric_limits<unsigned int>::max() ) {
fc::raw::pack( s, static_cast<uint8_t>( 254 ) );
fc::raw::pack( s, htole32(static_cast<uint32_t>( size ) ) );
} else {
fc::raw::pack( s, static_cast<uint8_t>( 255 ) );
fc::raw::pack( s, htole64( static_cast<uint64_t>( size ) ) );
}
}
template<typename Stream>
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<typename Stream>
inline void pack( Stream& s, const std::vector<char>& v )
{
pack_compact_size( s, v.size() );
if ( !v.empty() )
s.write( v.data(), v.size() );
}
template<typename Stream>
inline void unpack( Stream& s, std::vector<char>& v )
{
const auto size = unpack_compact_size( s );
v.resize( size );
if ( size )
s.read( v.data(), size );
}
template<typename Stream, typename T>
inline void pack( Stream& s, const T& val )
{
fc::raw::pack( s, val );
}
template<typename Stream, typename T>
inline void unpack( Stream& s, T& val )
{
fc::raw::unpack( s, val );
}
template<typename Stream>
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<typename Stream>
inline void unpack( Stream& s, out_point& op )
{
uint64_t hash_size = op.hash.data_size();
std::unique_ptr<char[]> 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<typename Stream>
inline void pack( Stream& s, const tx_in& in )
{
pack( s, in.prevout );
pack( s, in.scriptSig );
pack( s, in.nSequence );
}
template<typename Stream>
inline void unpack( Stream& s, tx_in& in )
{
unpack( s, in.prevout );
unpack( s, in.scriptSig );
unpack( s, in.nSequence );
}
template<typename Stream>
inline void pack( Stream& s, const tx_out& out )
{
pack( s, out.value );
pack( s, out.scriptPubKey );
}
template<typename Stream>
inline void unpack( Stream& s, tx_out& out )
{
unpack( s, out.value );
unpack( s, out.scriptPubKey );
}
template<typename Stream>
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<typename Stream>
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<char> script;
unpack( s, script );
in.scriptWitness.push_back( script );
}
}
}
unpack( s, tx.nLockTime );
}
inline std::vector<char> pack( const bitcoin_transaction& v, bool with_witness = true )
{
fc::datastream<size_t> ps;
pack( ps, v, with_witness );
std::vector<char> vec( ps.tellp() );
if( !vec.empty() ) {
fc::datastream<char*> ds( vec.data(), size_t( vec.size() ) );
pack( ds, v, with_witness );
}
return vec;
}
inline bitcoin_transaction unpack( const std::vector<char>& s )
{ try {
bitcoin_transaction tmp;
if( !s.empty() ) {
fc::datastream<const char*> ds( s.data(), size_t( s.size() ) );
unpack(ds, tmp);
}
return tmp;
} FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type","transaction" ) ) }
template<typename Stream>
inline void pack_tx_signature( Stream& s, const std::vector<char>& 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<typename Stream>
inline void pack_tx_witness_signature( Stream& s, const std::vector<char>& 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<size_t> ps;
for ( const auto in : tx.vin )
pack( ps, in.prevout );
std::vector<char> vec( ps.tellp() );
if ( vec.size() ) {
fc::datastream<char*> 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<size_t> ps;
for ( const auto in : tx.vin )
pack( ps, in.nSequence );
std::vector<char> vec( ps.tellp() );
if ( vec.size() ) {
fc::datastream<char*> 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<size_t> ps;
for ( const auto out : tx.vout )
pack( ps, out );
std::vector<char> vec( ps.tellp() );
if ( vec.size() ) {
fc::datastream<char*> 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 );
}
}

View file

@ -0,0 +1,52 @@
#pragma once
#include <sidechain/input_withdrawal_info.hpp>
#include <sidechain/bitcoin_transaction.hpp>
using namespace sidechain;
namespace sidechain {
class sidechain_condensing_tx
{
public:
sidechain_condensing_tx( const std::vector<info_for_vin>& vin_info, const std::vector<info_for_vout>& 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<info_for_vin>& vin_info );
void create_vouts_for_condensing_tx( const std::vector<info_for_vout>& 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;
};
}

View file

@ -0,0 +1,30 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
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)
)

View file

@ -0,0 +1,52 @@
#pragma once
#include <sidechain/types.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <graphene/chain/protocol/bitcoin_transaction.hpp>
#include <graphene/chain/protocol/operations.hpp>
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_vin>& info_for_vins );
bool check_info_for_vouts( const std::vector<info_for_vout_id_type>& 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<fc::sha256>& user_vin_identifiers );
bool check_reuse_vouts( const std::vector<info_for_vout_id_type>& user_vout_ids );
std::set<fc::sha256> pw_vin_ident;
std::set<fc::sha256> user_vin_ident;
std::set<info_for_vout_id_type> vout_ids;
graphene::chain::database& db;
};
}

View file

@ -0,0 +1,27 @@
#pragma once
#include <sidechain/types.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include <secp256k1.h>
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<char> privkey_sign( const bytes& privkey, const fc::sha256 &hash, const secp256k1_context_t* context_sign = nullptr );
std::vector<bytes> sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& 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<bytes>& redeem_scripts );
bool verify_sig( const bytes& sig, const bytes& pubkey, const bytes& msg, const secp256k1_context_t* context );
std::vector<std::vector<bytes>> sort_sigs( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const secp256k1_context_t* context );
}

View file

@ -0,0 +1,82 @@
#pragma once
#include <mutex>
#include <fc/optional.hpp>
namespace sidechain {
template<class T1>
class thread_safe_index
{
public:
using iterator = typename T1::iterator;
std::pair<iterator,bool> insert( const typename T1::value_type& value ) {
std::lock_guard<std::recursive_mutex> locker( lock );
return data.insert( value );
}
template<class T2, typename Key>
bool modify( const Key _key, const std::function<void( typename T1::value_type& e)>& func ) {
std::lock_guard<std::recursive_mutex> locker( lock );
const auto option = find_iterator<T2>( _key );
if( !option )
return false;
data.modify( data.iterator_to( **option ), [&func]( typename T1::value_type& obj ) { func( obj ); } );
return true;
}
template<class T2, typename Key>
bool remove( const Key _key ) {
std::lock_guard<std::recursive_mutex> locker( lock );
const auto option = find_iterator<T2>( _key );
if( !option )
return false;
data.erase( data.iterator_to( **option ) );
return true;
}
size_t size() {
std::lock_guard<std::recursive_mutex> locker( lock );
return data.size();
}
template<class T2, typename Key>
fc::optional<typename T1::value_type> find( const Key _key ) {
std::lock_guard<std::recursive_mutex> locker( lock );
auto& index = data.template get<T2>();
auto it = index.find( _key );
if( it != index.end() ) {
return fc::optional<typename T1::value_type>(*it);
}
return fc::optional<typename T1::value_type>();
}
template<class T2>
void safe_for(std::function<void(typename T1::template index<T2>::type::iterator itr1,
typename T1::template index<T2>::type::iterator itr2)> func) {
std::lock_guard<std::recursive_mutex> locker( lock );
auto& index = data.template get<T2>();
func(index.begin(), index.end());
}
private:
template<class T2, typename Key>
fc::optional<typename T1::template index<T2>::type::iterator> find_iterator( const Key _key ) {
auto& index = data.template get<T2>();
auto it = index.find( _key );
if( it == index.end() )
return fc::optional<typename T1::template index<T2>::type::iterator>();
return it;
}
std::recursive_mutex lock;
T1 data;
};
}

View file

@ -0,0 +1,60 @@
#pragma once
#include <map>
#include <vector>
#include <string>
#include <fc/reflect/reflect.hpp>
#include <graphene/chain/protocol/types.hpp>
namespace sidechain {
class bitcoin_transaction;
using bytes = std::vector<char>;
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<bitcoin_transaction, uint64_t>;
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) );

View file

@ -0,0 +1,15 @@
#pragma once
#include <sidechain/types.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/crypto/elliptic.hpp>
namespace sidechain {
bytes parse_hex( const std::string& str );
std::vector<bytes> get_pubkey_from_redeemScript( bytes script );
bytes public_key_data_to_bytes( const fc::ecc::public_key_data& key );
}

View file

@ -0,0 +1,207 @@
#include <sidechain/input_withdrawal_info.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <sidechain/bitcoin_transaction_confirmations.hpp>
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<bytes> input_withdrawal_info::get_redeem_scripts( const std::vector<info_for_vin>& info_vins )
{
std::vector<bytes> redeem_scripts;
const auto& bitcoin_address_idx = db.get_index_type<bitcoin_address_index>().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<uint64_t> input_withdrawal_info::get_amounts( const std::vector<info_for_vin>& info_vins )
{
std::vector<uint64_t> amounts;
for( const auto& v : info_vins ) {
amounts.push_back( v.out.amount );
}
return amounts;
}
fc::optional<info_for_vin> 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<info_for_vin>();
}
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<void( info_for_vin& e )>& func )
{
info_for_vins.modify<by_identifier>( obj.identifier, func );
}
void input_withdrawal_info::mark_as_used_vin( const info_for_vin& obj )
{
info_for_vins.modify<by_identifier>( 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<by_identifier>( 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<by_identifier>( obj.identifier );
}
fc::optional<info_for_vin> input_withdrawal_info::find_info_for_vin( fc::sha256 identifier )
{
return info_for_vins.find<by_identifier>( identifier );
}
std::vector<info_for_vin> input_withdrawal_info::get_info_for_vins()
{
std::vector<info_for_vin> result;
const auto max_vins = db.get_sidechain_params().maximum_condensing_tx_vins;
info_for_vins.safe_for<by_id_resend_not_used>( [&]( info_for_vin_index::index<by_id_resend_not_used>::type::iterator itr_b,
info_for_vin_index::index<by_id_resend_not_used>::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>([&](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<graphene::chain::info_for_vout_index>().indices().get< graphene::chain::by_id >();
auto itr = info_for_vout_idx.find( obj.id );
db.modify<graphene::chain::info_for_vout_object>( *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<graphene::chain::info_for_vout_index>().indices().get< graphene::chain::by_id >();
auto itr = info_for_vout_idx.find( obj.id );
db.modify<graphene::chain::info_for_vout_object>( *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<graphene::chain::info_for_vout_index>().indices().get< graphene::chain::by_id >();
auto itr = info_for_vout_idx.find( obj.id );
db.remove( *itr );
}
fc::optional<graphene::chain::info_for_vout_object> 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<graphene::chain::info_for_vout_index>().indices().get< graphene::chain::by_id >();
auto itr = info_for_vout_idx.find( id );
if( itr != info_for_vout_idx.end() )
return fc::optional<graphene::chain::info_for_vout_object>( *itr );
return fc::optional<graphene::chain::info_for_vout_object>();
}
size_t input_withdrawal_info::size_info_for_vouts()
{
const auto& info_for_vout_idx = db.get_index_type<graphene::chain::info_for_vout_index>().indices().get< graphene::chain::by_id >();
return info_for_vout_idx.size();
}
std::vector<info_for_vout> input_withdrawal_info::get_info_for_vouts()
{
std::vector<info_for_vout> result;
const auto& info_for_vout_idx = db.get_index_type<graphene::chain::info_for_vout_index>().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;
}
}

View file

@ -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" )

View file

@ -0,0 +1,140 @@
#include <sidechain/network/bitcoin_rpc_client.hpp>
#include <sstream>
#include <fc/network/ip.hpp>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
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<int64_t>();
}
}
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<std::string>();
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<int>();
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} );
}
}

View file

@ -0,0 +1,40 @@
#pragma once
#include <string>
#include <fc/network/http/connection.hpp>
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;
};
}

View file

@ -0,0 +1,52 @@
#pragma once
#include <sidechain/network/zmq_listener.hpp>
#include <sidechain/network/bitcoin_rpc_client.hpp>
#include <graphene/chain/database.hpp>
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<info_for_vin> extract_info_from_block( const std::string& _block );
void update_transaction_status( std::vector<fc::sha256> trx_for_check );
std::set<fc::sha256> get_valid_vins( const std::string tx_hash );
inline uint64_t parse_amount(std::string raw);
std::unique_ptr<zmq_listener> listener;
std::unique_ptr<bitcoin_rpc_client> bitcoin_client;
graphene::chain::database* db;
};
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <string>
#include <vector>
#include <fc/signals.hpp>
#include <zmq.hpp>
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<void( const std::string& )> block_received;
private:
void handle_zmq();
std::vector<zmq::message_t> receive_multipart();
std::string ip;
uint32_t zmq_port;
zmq::context_t ctx;
zmq::socket_t socket;
};
}

View file

@ -0,0 +1,202 @@
#include <sidechain/network/sidechain_net_manager.hpp>
#include <sidechain/serialize.hpp>
#include <sidechain/sidechain_parameters.hpp>
#include <fc/network/http/connection.hpp>
#include <fc/network/ip.hpp>
#include <thread>
#include <boost/property_tree/ptree.hpp>
#include <boost/property_tree/json_parser.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
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<zmq_listener>( new zmq_listener( _ip, _zmq ) );
bitcoin_client = std::unique_ptr<bitcoin_rpc_client>( 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<bitcoin_address_index>().indices().get<by_address>();
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<fc::sha256> trx_for_check;
const auto& confirmations_num = db->get_sidechain_params().confirmations_num;
db->bitcoin_confirmations.safe_for<by_hash>([&]( 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<by_hash>( 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<fc::sha256> 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<info_for_vin> 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<info_for_vin> result;
const auto& addr_idx = db->get_index_type<bitcoin_address_index>().indices().get<by_address>();
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<std::string>();
if( !addr_idx.count( address_base58 ) ) continue;
info_for_vin vin;
vin.out.hash_tx = tx.get_child("txid").get_value<std::string>();
vin.out.amount = parse_amount( o.second.get_child( "value" ).get_value<std::string>() );
vin.out.n_vout = o.second.get_child( "n" ).get_value<uint32_t>();
vin.address = address_base58;
result.push_back( vin );
}
}
}
return result;
}
std::set<fc::sha256> sidechain_net_manager::get_valid_vins( const std::string tx_hash )
{
const auto& confirmations_obj = db->bitcoin_confirmations.find<sidechain::by_hash>( fc::sha256( tx_hash ) );
FC_ASSERT( confirmations_obj.valid() );
std::set<fc::sha256> 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<fc::sha256> 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<by_hash>( trx, [&]( bitcoin_transaction_confirmations& obj ) {
obj.count_block = confirmations;
});
if( confirmations >= confirmations_num ) {
db->bitcoin_confirmations.modify<by_hash>( 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<fc::sha256> valid_vins;
if( !is_in_mempool ) {
valid_vins = get_valid_vins( trx.str() );
}
db->bitcoin_confirmations.modify<by_hash>( 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);
}
}

View file

@ -0,0 +1,45 @@
#include <sidechain/network/zmq_listener.hpp>
#include <boost/algorithm/hex.hpp>
#include <thread>
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::message_t> zmq_listener::receive_multipart()
{
std::vector<zmq::message_t> 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<char*>( msg[0].data() ), msg[0].size() );
const auto hash = boost::algorithm::hex( std::string( static_cast<char*>( msg[1].data() ), msg[1].size() ) );
block_received( hash );
}
}
}

View file

@ -0,0 +1,136 @@
#include <sidechain/primary_wallet_vout_manager.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/config.hpp>
namespace sidechain {
bool primary_wallet_vout_manager::is_max_vouts() const
{
const auto& PW_vout_idx = db.get_index_type<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().get_next_id().instance();
db.create<primary_wallet_vout_object>([&]( 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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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<graphene::chain::primary_wallet_vout_index>().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 );
}
}

View file

@ -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 <sidechain/segwit_addr.hpp>
#include <sidechain/bech32.hpp>
namespace
{
typedef std::vector<uint8_t> data;
/** Convert from one power-of-2 number base to another. */
template<int frombits, int tobits, bool pad>
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<int, data> decode(const std::string& hrp, const std::string& addr) {
std::pair<std::string, data> 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;
}
} }

View file

@ -0,0 +1,107 @@
#include <sidechain/sidechain_condensing_tx.hpp>
namespace sidechain {
sidechain_condensing_tx::sidechain_condensing_tx( const std::vector<info_for_vin>& vin_info, const std::vector<info_for_vout>& 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<info_for_vin>& 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<info_for_vout>& 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<bytes> 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 ) );
}
}

View file

@ -0,0 +1,214 @@
#include <sidechain/sidechain_proposal_checker.hpp>
#include <sidechain/sidechain_condensing_tx.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <graphene/chain/info_for_used_vin_object.hpp>
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<fc::sha256>& 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<info_for_vout_id_type>& 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<fc::sha256> user_vin_identifiers;
std::vector<info_for_vout_id_type> user_vout_ids;
auto get_bto_tx_info = [ & ]( fc::sha256 trx_id ) {
const auto& bto_itr_idx = db.get_index_type<bitcoin_transaction_index>().indices().get<graphene::chain::by_transaction_id>();
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<bitcoin_transaction_send_operation>::value ) {
bitcoin_transaction_send_operation btc_tx_send_op = op.get<bitcoin_transaction_send_operation>();
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<bitcoin_issue_operation>::value ) {
bitcoin_issue_operation btc_issue_op = op.get<bitcoin_issue_operation>();
for( const auto& id : btc_issue_op.transaction_ids ) {
if ( !get_bto_tx_info( id ) )
return false;
}
} else if ( op.which() == operation::tag<bitcoin_transaction_revert_operation>::value ) {
bitcoin_transaction_revert_operation btc_tx_revert_op = op.get<bitcoin_transaction_revert_operation>();
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_vin>& 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_id_type>& info_for_vout_ids )
{
const auto& info_for_vout_idx = db.get_index_type<info_for_vout_index>().indices().get<graphene::chain::by_id>();
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_for_vout> info_vouts;
const auto& info_for_vout_idx = db.get_index_type<info_for_vout_index>().indices().get<graphene::chain::by_id>();
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<account_index>().indices().get<graphene::chain::by_id>();
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<bitcoin_transaction_send_operation>();
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<bitcoin_address_index>().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<bitcoin_transaction_index>().indices().get<by_transaction_id>();
for( auto trx_info: op.transactions_info )
{
auto value = db.bitcoin_confirmations.find<sidechain::by_hash>( 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();
}
}

View file

@ -0,0 +1,125 @@
#include <sidechain/sign_bitcoin_transaction.hpp>
#include <sidechain/serialize.hpp>
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<size_t> 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<char> vec( ps.tellp() );
if ( !vec.empty() ) {
fc::datastream<char*> 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<char> 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<unsigned char*>( hash.data() ),
reinterpret_cast<unsigned char*>( sig.data() ),
&sig_len,
reinterpret_cast<const unsigned char*>( privkey.data() ),
secp256k1_nonce_function_rfc6979,
nullptr ) ); // TODO: replace assert with exception
sig.resize( sig_len );
return sig;
}
std::vector<bytes> sign_witness_transaction_part( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& 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<int64_t>( amounts[i] ), i, hash_type, true );
auto sig = privkey_sign( privkey, sighash, context_sign );
sig.push_back( static_cast<uint8_t>( hash_type ) );
signatures.push_back( sig );
}
return signatures;
}
void sign_witness_transaction_finalize( bitcoin_transaction& tx, const std::vector<bytes>& 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<unsigned char> sig_temp( sig.begin(), sig.end() );
std::vector<unsigned char> pubkey_temp( pubkey.begin(), pubkey.end() );
std::vector<unsigned char> 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<std::vector<bytes>> sort_sigs( const bitcoin_transaction& tx, const std::vector<bytes>& redeem_scripts,
const std::vector<uint64_t>& amounts, const secp256k1_context_t* context )
{
FC_ASSERT( redeem_scripts.size() == amounts.size() );
using data = std::pair<size_t, bytes>;
struct comp {
bool operator() (const data& lhs, const data& rhs) const { return lhs.first < rhs.first; }
};
std::vector<std::vector<bytes>> new_stacks;
for( size_t i = 0; i < redeem_scripts.size(); i++ ) {
const std::vector<bytes>& keys = get_pubkey_from_redeemScript( redeem_scripts[i] );
const auto& sighash = get_signature_hash( tx, redeem_scripts[i], static_cast<int64_t>( amounts[i] ), i, 1, true ).str();
bytes sighash_temp( parse_hex( sighash ) );
std::vector<bytes> stack( tx.vin[i].scriptWitness );
std::vector<bool> marker( tx.vin[i].scriptWitness.size(), false );
std::set<data, comp> 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<bytes> temp_sig;
for( auto s : sigs ) {
temp_sig.push_back( s.second );
}
new_stacks.push_back( temp_sig );
}
return new_stacks;
}
}

View file

@ -0,0 +1,35 @@
#include <sidechain/utils.hpp>
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<bytes> get_pubkey_from_redeemScript( bytes script )
{
FC_ASSERT( script.size() >= 37 );
script.erase( script.begin() );
script.erase( script.end() - 2, script.end() );
std::vector<bytes> 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;
}
}

View file

@ -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<bitcoin_address_object> 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)
)

View file

@ -273,9 +273,10 @@ public:
private:
void claim_registered_account(const account_object& account)
{
std::vector<std::string> 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 @@ private:
// 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<fc::ecc::private_key> 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 @@ public:
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<bitcoin_address_object> 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()
{

View file

@ -43,6 +43,10 @@
#include <graphene/chain/tournament_object.hpp>
#include <graphene/chain/match_object.hpp>
#include <graphene/chain/game_object.hpp>
#include <graphene/chain/info_for_vout_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <iostream>

View file

@ -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 )

View file

@ -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<uint32_t>( i ), static_cast<uint64_t>( 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<info_for_vin> vins;
info_for_vin pw_vin;
std::vector<info_for_vout> vouts;
full_btc_transaction full_tx;
};
}

View file

@ -0,0 +1,134 @@
#include <boost/test/unit_test.hpp>
#include <sidechain/bitcoin_address.hpp>
using namespace sidechain;
BOOST_AUTO_TEST_SUITE( bitcoin_address_tests )
fc::ecc::public_key_data create_public_key_data( const std::vector<char>& 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<char> public_key1 = parse_hex( "03db643710666b862e0a97f7edbe8ef40ec2c4a29ef995c431c21ca85e35000010" );
std::vector<char> public_key2 = parse_hex( "0320000d982c156a6f09df8c7674abddc2bb326533268ed03572916221b4417983" );
std::vector<char> public_key3 = parse_hex( "033619e682149aef0c3e2dee3dc5107dd78cb2c14bf0bd25b59056259fbb37ec3f" );
std::vector<char> address = parse_hex( "a91460cb986f0926e7c4ca1984ca9f56767da2af031e87" );
std::vector<char> 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<char> public_key1 = parse_hex( "03b3623117e988b76aaabe3d63f56a4fc88b228a71e64c4cc551d1204822fe85cb" );
std::vector<char> public_key2 = parse_hex( "03dd823066e096f72ed617a41d3ca56717db335b1ea47a1b4c5c9dbdd0963acba6" );
std::vector<char> public_key3 = parse_hex( "033d7c89bd9da29fa8d44db7906a9778b53121f72191184a9fee785c39180e4be1" );
std::vector<char> 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()

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,169 @@
#include <boost/test/unit_test.hpp>
#include <sidechain/utils.hpp>
#include <sidechain/bitcoin_transaction.hpp>
#include <sidechain/serialize.hpp>
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<fc::ecc::public_key_data>( 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()

View file

@ -0,0 +1,311 @@
#include <boost/test/unit_test.hpp>
#include "../common/database_fixture.hpp"
#include <fc/crypto/digest.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/sidechain_proposal_object.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
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<sidechain_proposal_index>().indices().get<by_proposal>();
BOOST_CHECK( !check_size || sidechain_proposal_idx.size() == 0 );
const flat_set<witness_id_type>& 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>( [&]( 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>( [&]( 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>( [&]( 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>( [&]( 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<info_for_vin>( env.vins.begin(), env.vins.end() - offset );
for( const auto& vout : std::vector<info_for_vout>( 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<sidechain_proposal_index>().indices().get<by_proposal>();
BOOST_CHECK_EQUAL( sidechain_proposal_idx.size(), 0 );
const auto& propose_send = create_op_and_prop<bitcoin_transaction_send_operation>( db, false );
const auto& propose_issue = create_op_and_prop<bitcoin_issue_operation>( db, false );
const auto& propose_revert = create_op_and_prop<bitcoin_transaction_revert_operation>( db, false );
create_op_and_prop<bitcoin_transaction_send_operation>( db, false );
create_op_and_prop<bitcoin_issue_operation>( db, false );
create_op_and_prop<bitcoin_transaction_revert_operation>( 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>( [&]( 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<bitcoin_issue_operation> ( db );
}
BOOST_AUTO_TEST_CASE( sidechain_fork_btc_send_op_test )
{
check_fork_prop<bitcoin_transaction_send_operation> ( db );
}
BOOST_AUTO_TEST_CASE( sidechain_fork_btc_revert_op_test )
{
check_fork_prop<bitcoin_transaction_revert_operation> ( 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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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<sidechain_proposal_index>().indices().get<by_proposal>().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()

View file

@ -0,0 +1,274 @@
#include <boost/test/unit_test.hpp>
#include <sidechain/input_withdrawal_info.hpp>
#include "../common/database_fixture.hpp"
#include <sidechain/types.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
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<uint32_t>( 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<uint32_t>( 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<uint32_t>( 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<char> 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<uint32_t>( 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<char> 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<uint32_t>( 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>( [&]( 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<uint32_t>( 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<uint64_t>( 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<uint64_t>( 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<uint64_t>( 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<uint32_t> 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<uint32_t>( 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<uint32_t>( 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()

View file

@ -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 <cstdlib>
#include <iostream>
#include <boost/test/included/unit_test.hpp>
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;
}

View file

@ -0,0 +1,158 @@
#include <boost/test/unit_test.hpp>
#include <sidechain/primary_wallet_vout_manager.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include "../common/database_fixture.hpp"
#include <sidechain/types.hpp>
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<primary_wallet_vout_index>().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<primary_wallet_vout_index>().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<primary_wallet_vout_index>().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<primary_wallet_vout_index>().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<primary_wallet_vout_index>().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<primary_wallet_vout_index>().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()

View file

@ -0,0 +1,254 @@
#include <boost/test/unit_test.hpp>
#include <sidechain/sidechain_condensing_tx.hpp>
#include <sidechain/bitcoin_script.hpp>
#include <fc/crypto/base58.hpp>
#include <fc/crypto/digest.hpp>
#include <iostream>
#include <numeric>
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_for_vin>& info_vins, std::vector<info_for_vout>& 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<uint32_t>( i );
vin.out.amount = static_cast<uint64_t>( 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<uint64_t>( 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<uint32_t>( 13 );
vin_info.out.amount = static_cast<uint64_t>( 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_for_vin> info_vins;
std::vector<info_for_vout> 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<uint32_t>( i ) );
bytes scriptSig = { 0x22, 0x0d };
BOOST_CHECK( tx.vin[i].scriptSig == scriptSig );
BOOST_CHECK( tx.vin[i].scriptWitness == std::vector<bytes>() );
BOOST_CHECK( tx.vout[i].value == static_cast<int64_t>( 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_for_vin> info_vins;
std::vector<info_for_vout> 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<uint32_t>( 13 ) );
bytes scriptSig = { 0x22, 0x0d };
BOOST_CHECK( tx.vin[0].scriptSig == bytes() );
BOOST_CHECK( tx.vout[0].value == static_cast<int64_t>( 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<int64_t>( 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_for_vin> info_vins;
std::vector<info_for_vout> 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<uint64_t> 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<int64_t>( 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<int64_t>( 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<int64_t>( ( 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_for_vin> info_vins;
std::vector<info_for_vout> 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<uint64_t> 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<int64_t>( ( info_vouts[i].amount - size_fee_user ) - witnesses_fee[i] ) );
}
}
BOOST_AUTO_TEST_CASE( subtract_fee_not_witness_vouts_tests )
{
std::vector<info_for_vin> info_vins;
std::vector<info_for_vout> 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<uint64_t> 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<int64_t>( ( 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_for_vin> info_vins;
std::vector<info_for_vout> 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<uint64_t> 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<int64_t>( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) );
}
}
BOOST_AUTO_TEST_CASE( subtract_fee_not_vins_tests )
{
std::vector<info_for_vin> info_vins;
std::vector<info_for_vout> 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<uint64_t> 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<int64_t>( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) );
}
}
BOOST_AUTO_TEST_SUITE_END()

View file

@ -0,0 +1,223 @@
#include <boost/test/unit_test.hpp>
#include "../common/database_fixture.hpp"
#include <fc/crypto/digest.hpp>
#include <graphene/chain/primary_wallet_vout_object.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
#include <graphene/chain/info_for_used_vin_object.hpp>
#include <graphene/chain/info_for_vout_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
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>([&]( 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>([&]( 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>([&]( 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>([&]( 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<bitcoin_address_index>().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<bitcoin_transaction_index>().indices().get<by_id>();
const auto& vins_info_idx = db.get_index_type<info_for_used_vin_index>().indices().get<by_id>();
const auto& vouts_info_idx = db.get_index_type<info_for_vout_index>().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<fc::sha256>() ) );
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<account_balance_index>().indices().get<by_account_asset>();
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<bitcoin_transaction_index>().indices().get<by_id>();
const auto& btc_addr_idx = db.get_index_type<bitcoin_address_index>().indices().get<by_address>();
const auto& vins_info_idx = db.get_index_type<info_for_used_vin_index>().indices().get<by_id>();
const auto& vouts_info_idx = db.get_index_type<info_for_vout_index>().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<fc::sha256>() ) );
{
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<bitcoin_transaction_index>().indices().get< by_id >();
const auto& vins_info_idx = db.get_index_type<info_for_used_vin_index>().indices().get< by_identifier >();
const auto& vouts_info_idx = db.get_index_type<info_for_vout_index>().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<fc::sha256>() ) );
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()

View file

@ -0,0 +1,386 @@
#include <boost/test/unit_test.hpp>
#include "../common/database_fixture.hpp"
#include <sidechain/sidechain_proposal_checker.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/proposal_evaluator.hpp>
#include <graphene/chain/witness_object.hpp>
#include <graphene/chain/bitcoin_address_object.hpp>
#include <graphene/chain/bitcoin_transaction_object.hpp>
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<witness_id_type>& active_witnesses = db.get_global_properties().active_witnesses;
const auto& proposal_idx = db.get_index_type<graphene::chain::proposal_index>().indices().get< graphene::chain::by_id >();
transaction_evaluation_state context(&db);
auto propose = db.create<proposal_object>( [&]( 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<graphene::chain::witness_index>().indices().get<graphene::chain::by_id>();
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<graphene::chain::proposal_index>().indices().get< graphene::chain::by_id >();
auto propose = db.create<proposal_object>( [&]( 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<graphene::chain::witness_index>().indices().get<graphene::chain::by_id>();
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<graphene::chain::proposal_index>().indices().get< graphene::chain::by_id >();
auto propose = db.create<proposal_object>( [&]( 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>( [&]( 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>( [&]( 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>( [&]( 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<fc::sha256> 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>( [&]( 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<by_missing_and_not_used>::type::iterator;
std::vector< revert_trx_info > transactions_info;
const auto& btc_trx_idx = db.get_index_type<graphene::chain::bitcoin_transaction_index>().indices().get<graphene::chain::by_transaction_id>();
BOOST_CHECK_EQUAL( btc_trx_idx.size() , amount );
db.bitcoin_confirmations.safe_for<by_missing_and_not_used>([&]( 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<fc::sha256> 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>( [&]( 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()