diff --git a/libraries/chain/include/graphene/chain/info_for_vout_object.hpp b/libraries/chain/include/graphene/chain/info_for_vout_object.hpp index 8f0c8ab1..5c49ad29 100644 --- a/libraries/chain/include/graphene/chain/info_for_vout_object.hpp +++ b/libraries/chain/include/graphene/chain/info_for_vout_object.hpp @@ -3,6 +3,7 @@ #include #include #include +#include namespace graphene { namespace chain { @@ -22,10 +23,9 @@ class info_for_vout_object : public abstract_object info_for_vout_id_type get_id()const { return id; } - account_id_type payer; - sidechain::payment_type addr_type; - std::string data; - uint64_t amount; + account_id_type payer; + sidechain::bitcoin_address address; + uint64_t amount; bool created = false; }; @@ -45,5 +45,5 @@ typedef generic_index } } // graphene::chain -FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(addr_type)(data)(amount)(created) ) +FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(address)(amount)(created) ) diff --git a/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp b/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp index bd9e48fa..3a72a91d 100644 --- a/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp +++ b/libraries/chain/include/graphene/chain/withdraw_pbtc_evaluator.hpp @@ -15,7 +15,7 @@ public: void_result do_evaluate(const withdraw_pbtc_operation& op); - object_id_type do_apply(const withdraw_pbtc_operation& op); + void_result do_apply(const withdraw_pbtc_operation& op); void reserve_issue( const withdraw_pbtc_operation& op ); diff --git a/libraries/chain/withdraw_pbtc_evaluator.cpp b/libraries/chain/withdraw_pbtc_evaluator.cpp index 0ba3a8e5..aaa1fb2e 100644 --- a/libraries/chain/withdraw_pbtc_evaluator.cpp +++ b/libraries/chain/withdraw_pbtc_evaluator.cpp @@ -21,20 +21,11 @@ void_result withdraw_pbtc_evaluator::do_evaluate(const withdraw_pbtc_operation& return void_result(); } -object_id_type withdraw_pbtc_evaluator::do_apply(const withdraw_pbtc_operation& op) +void_result withdraw_pbtc_evaluator::do_apply(const withdraw_pbtc_operation& op) { - database& d = db(); - - auto id = d.create( [&]( info_for_vout_object& obj ) { - obj.payer = op.payer; - obj.addr_type = type; - obj.data = op.data; - obj.amount = op.amount; - } ).get_id(); - + db().i_w_info.insert_info_for_vout( op.payer, op.data, op.amount ); reserve_issue( op ); - - return id; + return void_result(); } void withdraw_pbtc_evaluator::reserve_issue( const withdraw_pbtc_operation& op ) diff --git a/libraries/sidechain/bitcoin_address.cpp b/libraries/sidechain/bitcoin_address.cpp index c9f46317..ce115172 100644 --- a/libraries/sidechain/bitcoin_address.cpp +++ b/libraries/sidechain/bitcoin_address.cpp @@ -146,7 +146,7 @@ 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[keys_required - 1] ); + 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(); @@ -154,7 +154,7 @@ void btc_multisig_address::create_redeem_script() 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[witnesses_keys.size() - 1] ); + redeem_script.push_back( op_num[witnesses_keys.size() - 1] ); redeem_script.push_back( OP_CHECKMULTISIG ); } diff --git a/libraries/sidechain/bitcoin_script.cpp b/libraries/sidechain/bitcoin_script.cpp new file mode 100644 index 00000000..367e5ff8 --- /dev/null +++ b/libraries/sidechain/bitcoin_script.cpp @@ -0,0 +1,60 @@ +#include +#include + +namespace sidechain { + +script_builder& script_builder::operator<<( op opcode ) +{ + const auto op_byte = static_cast( opcode ); + if ( op_byte < 0 || op_byte > 0xff ) + throw std::runtime_error( "script_builder::operator<<(OP): invalid opcode" ); + script.push_back( op_byte ); + return *this; +} + +script_builder& script_builder::operator<<( uint8_t number ) +{ + FC_ASSERT( 0 <= number && number <= 16 ); + + if ( number == 0 ) + script.push_back( static_cast( op::_0 ) ); + else + script.push_back( static_cast( op::_1 ) + number - 1 ); + + return *this; +} + +script_builder& script_builder::operator<<( size_t size ) +{ + sidechain::write_compact_size( script, size ); + return *this; +} + +script_builder& script_builder::operator<<( const bytes& sc ) { + sidechain::write_compact_size( script, sc.size() ); + script.insert( script.end(), sc.begin(), sc.end() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::sha256& hash ) +{ + sidechain::write_compact_size( script, hash.data_size() ); + script.insert( script.end(), hash.data(), hash.data() + hash.data_size() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::ripemd160& hash ) +{ + sidechain::write_compact_size( script, hash.data_size() ); + script.insert( script.end(), hash.data(), hash.data() + hash.data_size() ); + return *this; +} + +script_builder& script_builder::operator<<( const fc::ecc::public_key_data& pubkey_data ) +{ + sidechain::write_compact_size( script, pubkey_data.size() ); + script.insert( script.end(), pubkey_data.begin(), pubkey_data.begin() + pubkey_data.size() ); + return *this; +} + +} diff --git a/libraries/sidechain/bitcoin_transaction.cpp b/libraries/sidechain/bitcoin_transaction.cpp new file mode 100644 index 00000000..1c57de2b --- /dev/null +++ b/libraries/sidechain/bitcoin_transaction.cpp @@ -0,0 +1,235 @@ +#include +#include +#include + +namespace sidechain { + +bool tx_out::is_p2wsh() const +{ + if( scriptPubKey.size() == 34 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x20) ) { + return true; + } + return false; +} + +bool tx_out::is_p2wpkh() const +{ + if( scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x14) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pkh() const +{ + if( scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast(0x76) && scriptPubKey[1] == static_cast(0xa9) && + scriptPubKey[2] == static_cast(0x14) && scriptPubKey[23] == static_cast(0x88) && scriptPubKey[24] == static_cast(0xac) ) { + return true; + } + return false; +} + +bool tx_out::is_p2sh() const +{ + if( scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast(0xa9) && + scriptPubKey[1] == static_cast(0x14) && scriptPubKey[22] == static_cast(0x87) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pk() const +{ + if( scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast(0x21) && scriptPubKey[34] == static_cast(0xac) ) { + return true; + } + return false; +} + +bytes tx_out::get_data_or_script() const +{ + if( is_p2pkh() ) { + return bytes( scriptPubKey.begin() + 3, scriptPubKey.begin() + 23 ); + } else if( is_p2sh() || is_p2wpkh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 22 ); + } else if( is_p2wsh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 34 ); + } else if( is_p2pk() ) { + return bytes( scriptPubKey.begin() + 1, scriptPubKey.begin() + 34 ); + } + return scriptPubKey; +} + +fc::sha256 bitcoin_transaction::get_hash() const +{ + const auto bytes = pack( *this, true) ; + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +fc::sha256 bitcoin_transaction::get_txid() const +{ + const auto bytes = pack( *this, false ); + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +size_t bitcoin_transaction::get_vsize() const +{ + static const auto witness_scale_factor = 4; + + fc::datastream no_wit_ds; + pack( no_wit_ds, *this, false ); + + fc::datastream wit_ds; + pack( wit_ds, *this, true ); + + const size_t weight = no_wit_ds.tellp() * ( witness_scale_factor - 1 ) + wit_ds.tellp(); + const size_t vsize = ( weight + witness_scale_factor - 1 ) / witness_scale_factor; + + return vsize; +} + +void bitcoin_transaction_builder::set_version( int32_t version ) +{ + tx.nVersion = version; +} + +void bitcoin_transaction_builder::set_locktime(uint32_t lock_time) +{ + tx.nLockTime = lock_time; +} + +void bitcoin_transaction_builder::add_in( payment_type type, const fc::sha256& txid, uint32_t n_out, const bytes& script_code, bool front, uint32_t sequence ) +{ + out_point prevout; + prevout.hash = txid; + prevout.n = n_out; + + tx_in txin; + txin.prevout = prevout; + txin.nSequence = sequence; + + add_in( type, txin, script_code, front ); +} + +void bitcoin_transaction_builder::add_in( payment_type type, tx_in txin, const bytes& script_code, bool front ) +{ + switch ( type ) { + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + break; + default:{ + if( txin.prevout.hash == fc::sha256("0000000000000000000000000000000000000000000000000000000000000000") ) { //coinbase + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + } + break; + } + } + + if( front ) { + tx.vin.insert( tx.vin.begin(), txin ); + } else { + tx.vin.push_back( txin ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const std::string& base58_address, bool front ) +{ + // TODO: add checks + const auto address_bytes = fc::from_base58(base58_address); + add_out( type, amount, bytes( address_bytes.begin() + 1, address_bytes.begin() + 1 + 20 ), front ); +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const fc::ecc::public_key_data& pubkey, bool front ) +{ + FC_ASSERT( is_payment_to_pubkey( type ) ); + + if ( type == payment_type::P2PK ) { + const auto pubkey_bytes = bytes( pubkey.begin(), pubkey.begin() + pubkey.size() ); + add_out( type, amount, pubkey_bytes, front ); + } else { + const auto hash256 = fc::sha256::hash( pubkey.begin(), pubkey.size() ); + const auto hash160 = fc::ripemd160::hash( hash256.data(), hash256.data_size() ); + add_out( type, amount, bytes( hash160.data(), hash160.data() + hash160.data_size() ), front ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const bytes& script_code, bool front ) +{ + tx_out out; + out.value = amount; + out.scriptPubKey = get_script_pubkey( type, script_code ); + + if( front ) { + tx.vout.insert( tx.vout.begin(), out ); + } else { + tx.vout.push_back( out ); + } +} + +void bitcoin_transaction_builder::add_out_all_type( const uint64_t& amount, const bitcoin_address& address, bool front ) +{ + switch( address.get_type() ) { + case payment_type::P2PK: { + bytes raw_address( address.get_raw_address() ); + fc::ecc::public_key_data public_key; + std::copy( raw_address.begin(), raw_address.end(), public_key.begin() ); + add_out( address.get_type(), amount, public_key, front ); + break; + } + case payment_type::P2PKH: + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: { + add_out( address.get_type(), amount, address.get_address(), front ); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + add_out( address.get_type(), amount, address.get_raw_address(), front ); + break; + } + } +} + +inline bool bitcoin_transaction_builder::is_payment_to_pubkey( payment_type type ) +{ + switch ( type ) { + case payment_type::P2PK: + case payment_type::P2PKH: + case payment_type::P2WPKH: + case payment_type::P2SH_WPKH: + return true; + default: + return false; + } +} + +bytes bitcoin_transaction_builder::get_script_pubkey( payment_type type, const bytes& script_code ) +{ + switch ( type ) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << script_code; + case payment_type::P2PK: + return script_builder() << script_code << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << script_code << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << script_code << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << script_code; + default: + return script_code; + } +} + +} diff --git a/libraries/sidechain/include/sidechain/bitcoin_address.hpp b/libraries/sidechain/include/sidechain/bitcoin_address.hpp index a6536201..08225439 100644 --- a/libraries/sidechain/include/sidechain/bitcoin_address.hpp +++ b/libraries/sidechain/include/sidechain/bitcoin_address.hpp @@ -8,7 +8,7 @@ using namespace graphene::chain; namespace sidechain { -const bytes op = {0x51,0x52,0x53,0x54,0x55,0x56,0x57,0x58,0x59,0x5a,0x5b,0x5c,0x5d,0x5e,0x5f}; // OP_1 - OP_15 +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 { diff --git a/libraries/sidechain/include/sidechain/bitcoin_script.hpp b/libraries/sidechain/include/sidechain/bitcoin_script.hpp new file mode 100644 index 00000000..1bd8e061 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_script.hpp @@ -0,0 +1,69 @@ +#pragma once + +#include + +namespace sidechain { + +enum class op { + // push value + _0 = 0x00, + _1 = 0x51, + _2 = 0x52, + _3 = 0x53, + _4 = 0x54, + _5 = 0x55, + _6 = 0x56, + _7 = 0x57, + _8 = 0x58, + _9 = 0x59, + _10 = 0x5a, + _11 = 0x5b, + _12 = 0x5c, + _13 = 0x5d, + _14 = 0x5e, + _15 = 0x5f, + _16 = 0x60, + + // control + RETURN = 0x6a, + + // stack ops + DUP = 0x76, + + // bit logic + EQUAL = 0x87, + EQUALVERIFY = 0x88, + + // crypto + HASH160 = 0xa9, + CHECKSIG = 0xac, + CHECKMULTISIG = 0xae, +}; + +class script_builder { + +public: + + script_builder& operator<<( op opcode ); + + script_builder& operator<<( uint8_t number ); + + script_builder& operator<<( size_t size ); + + script_builder& operator<<( const bytes& sc ); + + script_builder& operator<<( const fc::sha256& hash ); + + script_builder& operator<<( const fc::ripemd160& hash ); + + script_builder& operator<<( const fc::ecc::public_key_data& pubkey_data ); + + operator bytes() const { return std::move( script ); } + +private: + + bytes script; + +}; + +} diff --git a/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp b/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp new file mode 100644 index 00000000..180a6db3 --- /dev/null +++ b/libraries/sidechain/include/sidechain/bitcoin_transaction.hpp @@ -0,0 +1,247 @@ +#pragma once +#include +#include + +namespace sidechain { + +struct out_point { + fc::sha256 hash; + uint32_t n = 0; + out_point() = default; + out_point( fc::sha256 _hash, uint32_t _n ) : hash( _hash ), n( _n ) {} +}; + +struct tx_in { + out_point prevout; + bytes scriptSig; + uint32_t nSequence = 0xffffffff; + std::vector scriptWitness; +}; + +struct tx_out { + int64_t value = 0; + bytes scriptPubKey; + + 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 { + int32_t nVersion = 1; + std::vector vin; + std::vector vout; + uint32_t nLockTime = 0; + + fc::sha256 get_hash() const; + fc::sha256 get_txid() const; + size_t get_vsize() const; +}; + +class bitcoin_transaction_builder +{ + +public: + + bitcoin_transaction_builder() = default; + + bitcoin_transaction_builder( const bitcoin_transaction _tx ) : tx( _tx ) {} + + void set_version( int32_t version ); + + void set_locktime( uint32_t lock_time ); + + void add_in( payment_type type, const fc::sha256& txid, uint32_t n_out, + const bytes& script_code, bool front = false, uint32_t sequence = 0xffffffff ); + + void add_in( payment_type type, tx_in txin, const bytes& script_code, bool front = false ); + + void add_out( payment_type type, int64_t amount, const std::string& base58_address, bool front = false ); + + void add_out( payment_type type, int64_t amount, const fc::ecc::public_key_data& pubkey, bool front = false ); + + void add_out( payment_type type, int64_t amount, const bytes& script_code, bool front = false ); + + void add_out_all_type( const uint64_t& amount, const bitcoin_address& address, bool front = false ); + + bitcoin_transaction get_transaction() const { return tx; } + +private: + + inline bool is_payment_to_pubkey( payment_type type ); + + bytes get_script_pubkey( payment_type type, const bytes& script_code ); + + bitcoin_transaction tx; + +}; + +template +inline void pack( Stream& s, const out_point& op ) +{ + fc::sha256 reversed( op.hash ); + std::reverse( reversed.data(), reversed.data() + reversed.data_size() ); + s.write( reversed.data(), reversed.data_size() ); + pack( s, op.n ); +} + +template +inline void unpack( Stream& s, out_point& op ) +{ + uint64_t hash_size = op.hash.data_size(); + std::unique_ptr hash_data( new char[hash_size] ); + s.read( hash_data.get(), hash_size ); + std::reverse( hash_data.get(), hash_data.get() + hash_size ); + + op.hash = fc::sha256( hash_data.get(), hash_size ); + unpack( s, op.n ); +} + +template +inline void pack( Stream& s, const tx_in& in ) +{ + pack( s, in.prevout ); + pack( s, in.scriptSig ); + pack( s, in.nSequence ); +} + +template +inline void unpack( Stream& s, tx_in& in ) +{ + unpack( s, in.prevout ); + unpack( s, in.scriptSig ); + unpack( s, in.nSequence ); +} + +template +inline void pack( Stream& s, const tx_out& out ) +{ + pack( s, out.value ); + pack( s, out.scriptPubKey ); +} + +template +inline void unpack( Stream& s, tx_out& out ) +{ + unpack( s, out.value ); + unpack( s, out.scriptPubKey ); +} + +template +inline void pack( Stream& s, const bitcoin_transaction& tx, bool with_witness = true ) +{ + uint8_t flags = 0; + + if ( with_witness ) { + for ( const auto& in : tx.vin ) { + if ( !in.scriptWitness.empty() ) { + flags |= 1; + break; + } + } + } + + pack( s, tx.nVersion ); + + if ( flags ) { + pack_compact_size( s, 0 ); + pack( s, flags ); + } + + pack_compact_size( s, tx.vin.size() ); + for ( const auto& in : tx.vin ) + pack( s, in ); + + pack_compact_size( s, tx.vout.size() ); + for ( const auto& out : tx.vout ) + pack( s, out ); + + if ( flags & 1 ) { + for ( const auto in : tx.vin ) { + pack_compact_size( s, in.scriptWitness.size() ); + for ( const auto& sc : in.scriptWitness ) + pack( s, sc ); + } + } + + pack( s, tx.nLockTime ); +} + +template +inline void unpack( Stream& s, bitcoin_transaction& tx ) { + uint8_t flags = 0; + + unpack( s, tx.nVersion ); + + auto vin_size = unpack_compact_size( s ); + if ( vin_size == 0 ) { + unpack( s, flags ); + vin_size = unpack_compact_size( s ); + } + + tx.vin.reserve( vin_size ); + for ( size_t i = 0; i < vin_size; i++ ) { + tx_in in; + unpack( s, in ); + tx.vin.push_back( in ); + } + + const auto vout_size = unpack_compact_size( s ); + tx.vout.reserve( vout_size ); + for ( size_t i = 0; i < vout_size; i++ ) { + tx_out out; + unpack( s, out ); + tx.vout.push_back( out ); + } + + if ( flags & 1 ) { + for ( auto& in : tx.vin ) { + uint64_t stack_size = unpack_compact_size( s ); + in.scriptWitness.reserve( stack_size ); + for ( uint64_t i = 0; i < stack_size; i++ ) { + std::vector script; + unpack( s, script ); + in.scriptWitness.push_back( script ); + } + } + } + + unpack( s, tx.nLockTime ); +} + +inline std::vector pack( const bitcoin_transaction& v, bool with_witness = true ) { + fc::datastream ps; + pack( ps, v, with_witness ); + std::vector vec( ps.tellp() ); + + if( !vec.empty() ) { + fc::datastream ds( vec.data(), size_t(vec.size()) ); + pack( ds, v, with_witness ); + } + return vec; +} + +inline bitcoin_transaction unpack( const std::vector& s ) +{ try { + bitcoin_transaction tmp; + if( !s.empty() ) { + fc::datastream ds( s.data(), size_t(s.size()) ); + unpack(ds, tmp); + } + return tmp; +} FC_RETHROW_EXCEPTIONS( warn, "error unpacking ${type}", ("type","transaction" ) ) } + +} + +FC_REFLECT( sidechain::out_point, (hash)(n) ) +FC_REFLECT( sidechain::tx_in, (prevout)(scriptSig)(nSequence)(scriptWitness) ) +FC_REFLECT( sidechain::tx_out, (value)(scriptPubKey) ) +FC_REFLECT( sidechain::bitcoin_transaction, (nVersion)(vin)(vout)(nLockTime) ) diff --git a/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp b/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp index 13275f9b..c1955158 100644 --- a/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp +++ b/libraries/sidechain/include/sidechain/input_withdrawal_info.hpp @@ -13,7 +13,6 @@ using boost::multi_index_container; using namespace boost::multi_index; -using info_for_vout = graphene::chain::info_for_vout_object; namespace graphene { namespace chain { class database; } } @@ -83,7 +82,7 @@ public: std::vector get_info_for_vins(); - void insert_info_for_vout( const graphene::chain::account_id_type& payer, const payment_type addr_type, const std::string& data, const uint64_t& amount ); + 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 ); diff --git a/libraries/sidechain/include/sidechain/serialize.hpp b/libraries/sidechain/include/sidechain/serialize.hpp new file mode 100644 index 00000000..2c9c2041 --- /dev/null +++ b/libraries/sidechain/include/sidechain/serialize.hpp @@ -0,0 +1,104 @@ +#pragma once + +#include + +namespace sidechain { + +inline void write_compact_size(bytes& vec, size_t size) { + bytes sb; + sb.reserve(2); + if (size < 253) { + sb.insert(sb.end(), static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + uint16_t tmp = htole16(static_cast(size)); + sb.insert(sb.end(), static_cast(253)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else if (size <= std::numeric_limits::max()) { + uint32_t tmp = htole32(static_cast(size)); + sb.insert(sb.end(), static_cast(254)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } else { + uint64_t tmp = htole64(static_cast(size)); + sb.insert(sb.end(), static_cast(255)); + sb.insert(sb.end(), reinterpret_cast(tmp), reinterpret_cast(tmp) + sizeof(tmp)); + } + vec.insert(vec.end(), sb.begin(), sb.end()); +} + +template +inline void pack_compact_size(Stream& s, size_t size) { + if (size < 253) { + fc::raw::pack(s, static_cast(size)); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(253)); + fc::raw::pack(s, htole16(static_cast(size))); + } else if (size <= std::numeric_limits::max()) { + fc::raw::pack(s, static_cast(254)); + fc::raw::pack(s, htole32(static_cast(size))); + } else { + fc::raw::pack(s, static_cast(255)); + fc::raw::pack(s, htole64(static_cast(size))); + } +} + +template +inline uint64_t unpack_compact_size(Stream& s) { + uint8_t size; + uint64_t size_ret; + + fc::raw::unpack(s, size); + + if (size < 253) { + size_ret = size; + } else if (size == 253) { + uint16_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le16toh(tmp); + if (size_ret < 253) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else if (size == 254) { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le32toh(tmp); + if (size_ret < 0x10000u) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } else { + uint32_t tmp; + fc::raw::unpack(s, tmp); + size_ret = le64toh(tmp); + if (size_ret < 0x100000000ULL) + FC_THROW_EXCEPTION(fc::parse_error_exception, "non-canonical unpack_compact_size()"); + } + + if (size_ret > 0x08000000) + FC_THROW_EXCEPTION(fc::parse_error_exception, "unpack_compact_size(): size too large"); + + return size_ret; +} + +template +inline void pack(Stream& s, const std::vector& v) { + pack_compact_size(s, v.size()); + if (!v.empty()) + s.write(v.data(), v.size()); +} + +template +inline void unpack(Stream& s, std::vector& v) { + const auto size = unpack_compact_size(s); + v.resize(size); + if (size) + s.read(v.data(), size); +} + +template +inline void pack(Stream& s, const T& val) { + fc::raw::pack(s, val); +} + +template +inline void unpack(Stream& s, T& val) { + fc::raw::unpack(s, val); +} + +} diff --git a/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp b/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp new file mode 100644 index 00000000..b0185d2d --- /dev/null +++ b/libraries/sidechain/include/sidechain/sidechain_condensing_tx.hpp @@ -0,0 +1,74 @@ +#pragma once +#include +#include + +using namespace sidechain; + +namespace sidechain { + +// void example() +// { +// std::vector vin_infos { ... }; +// std::vector vout_infos { ... }; + +// sidechain_condensing_tx sct( vin_infos, vout_infos ); + +// if( pw_vin_info ) { +// sct.create_pw_vin( pw_vin_info ); +// } + +// if( vout_infos.size() ) { +// sct.create_vouts_for_witness_fee( keys ); +// } + +// if( ( vin_infos.sum() - vout_infos.sum() ) > 0 ) { +// sct.create_pw_vout( vin_infos.sum() - vout_infos.sum(), bytes{ 0x0d, 0x0d, 0x0d } ); +// } +// } + +class sidechain_condensing_tx +{ + +public: + + sidechain_condensing_tx( const std::vector& vin_info, const std::vector& vout_info ); + + void create_pw_vin( const info_for_vin& vin_info ); + + void create_pw_vout( const uint64_t amount, const bytes& wit_script_out ); + + void create_vouts_for_witness_fee( const accounts_keys& witness_active_keys ); + + uint64_t get_estimate_tx_size( size_t number_witness ) const; + + void subtract_fee( const uint64_t& fee, const double& witness_percentage ); + + bitcoin_transaction get_transaction() const { return tb.get_transaction(); } + + uint64_t get_amount_vins() { return amount_vins; } + + uint64_t get_amount_transfer_to_bitcoin() { return amount_transfer_to_bitcoin; } + +private: + + void create_vins_for_condensing_tx( const std::vector& vin_info ); + + void create_vouts_for_condensing_tx( const std::vector& vout_info ); + + uint32_t count_transfer_vin = 0; + + uint32_t count_transfer_vout = 0; + + uint32_t count_witness_vout = 0; + + uint64_t amount_vins = 0; + + uint64_t amount_transfer_to_bitcoin = 0; + + bitcoin_transaction_builder tb; + +}; + +int64_t get_estimated_fee( size_t tx_vsize, uint64_t estimated_feerate ); // move db_sidechain + +} diff --git a/libraries/sidechain/include/sidechain/types.hpp b/libraries/sidechain/include/sidechain/types.hpp index 2add508c..16f0d8b0 100644 --- a/libraries/sidechain/include/sidechain/types.hpp +++ b/libraries/sidechain/include/sidechain/types.hpp @@ -11,6 +11,7 @@ namespace sidechain { using bytes = std::vector; using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >; +using info_for_vout = graphene::chain::info_for_vout_object; enum class payment_type { diff --git a/libraries/sidechain/input_withdrawal_info.cpp b/libraries/sidechain/input_withdrawal_info.cpp index bd807d9e..866f442c 100644 --- a/libraries/sidechain/input_withdrawal_info.cpp +++ b/libraries/sidechain/input_withdrawal_info.cpp @@ -65,12 +65,11 @@ std::vector input_withdrawal_info::get_info_for_vins() return result; } -void input_withdrawal_info::insert_info_for_vout( const graphene::chain::account_id_type& payer, const payment_type addr_type, const std::string& data, const uint64_t& amount ) +void input_withdrawal_info::insert_info_for_vout( const graphene::chain::account_id_type& payer, const std::string& data, const uint64_t& amount ) { db.create([&](graphene::chain::info_for_vout_object& obj) { obj.payer = payer; - obj.addr_type = addr_type; - obj.data = data; + obj.address = bitcoin_address( data ); obj.amount = amount; }); } @@ -109,8 +108,7 @@ std::vector input_withdrawal_info::get_info_for_vouts() for(size_t i = 0; i < 5 && itr != info_for_vout_idx.end() && !itr->created; i++) { info_for_vout vout; vout.payer = itr->payer; - vout.addr_type = itr->addr_type; - vout.data = itr->data; + vout.address = itr->address; vout.amount = itr->amount; result.push_back( vout ); diff --git a/libraries/sidechain/sidechain_condensing_tx.cpp b/libraries/sidechain/sidechain_condensing_tx.cpp new file mode 100644 index 00000000..efdbe4b5 --- /dev/null +++ b/libraries/sidechain/sidechain_condensing_tx.cpp @@ -0,0 +1,113 @@ +#include + +namespace sidechain { + +sidechain_condensing_tx::sidechain_condensing_tx( const std::vector& vin_info, const std::vector& vout_info ) +{ + create_vins_for_condensing_tx( vin_info ); + create_vouts_for_condensing_tx( vout_info ); +} + +void sidechain_condensing_tx::create_pw_vin( const info_for_vin& vin_info ) +{ + 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, true ); + + amount_vins += vin_info.out.amount; +} + +void sidechain_condensing_tx::create_pw_vout( const uint64_t amount, const bytes& wit_script_out ) +{ + tb.add_out( payment_type::P2WSH, amount, bytes(wit_script_out.begin() + 2, wit_script_out.end()), true ); +} + +void sidechain_condensing_tx::create_vins_for_condensing_tx( const std::vector& vin_info ) +{ + for( const auto& info : vin_info ) { + bytes witness_script_temp = {0x22}; + witness_script_temp.insert( witness_script_temp.end(), info.script.begin(), info.script.end() ); + tb.add_in( payment_type::P2SH_WSH, fc::sha256( info.out.hash_tx ), info.out.n_vout, witness_script_temp ); + amount_vins += info.out.amount; + count_transfer_vin++; + } +} + +void sidechain_condensing_tx::create_vouts_for_condensing_tx( const std::vector& vout_info ) +{ + for( const auto& info : vout_info ) { + tb.add_out_all_type( info.amount, info.address ); + amount_transfer_to_bitcoin += info.amount; + count_transfer_vout++; + } +} + +void sidechain_condensing_tx::create_vouts_for_witness_fee( const accounts_keys& witness_active_keys ) +{ + for( auto& key : witness_active_keys ) { + tb.add_out( payment_type::P2PK, 0, key.second, true ); + count_witness_vout++; + } +} + +uint64_t sidechain_condensing_tx::get_estimate_tx_size( size_t number_witness ) const +{ + bytes temp_sig(72, 0x00); + bytes temp_key(34, 0x00); + bytes temp_script(3, 0x00); + for(size_t i = 0; i < number_witness; i++) { + temp_script.insert(temp_script.begin() + 1, temp_key.begin(), temp_key.end()); + } + + std::vector temp_scriptWitness = { {},{temp_sig},{temp_sig},{temp_sig},{temp_sig},{temp_sig},{temp_script} }; + bitcoin_transaction temp_tx = get_transaction(); + for( auto& vin : temp_tx.vin ) { + vin.scriptWitness = temp_scriptWitness; + } + + return temp_tx.get_vsize(); +} + +void sidechain_condensing_tx::subtract_fee( const uint64_t& fee, const double& witness_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; + } + + uint64_t fee_witnesses = 0; + size_t offset = tx.vout.size() > ( count_witness_vout + count_transfer_vout ) ? 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 * witness_percentage; + 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 = tx.vout.size() > ( count_witness_vout + count_transfer_vout ) ? 1 + count_witness_vout : count_witness_vout; + offset = tx.vout.size() > ( count_witness_vout + count_transfer_vout ) ? 1 : 0; + FC_ASSERT( fee_witness > 0 ); + for( ; offset < limit; offset++ ) { + tx.vout[offset].value += fee_witness; + } + } + + tb = std::move( bitcoin_transaction_builder( tx ) ); +} + +int64_t 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 ); +} + +} diff --git a/tests/tests/bitcoin_transaction_tests.cpp b/tests/tests/bitcoin_transaction_tests.cpp new file mode 100644 index 00000000..89bc331e --- /dev/null +++ b/tests/tests/bitcoin_transaction_tests.cpp @@ -0,0 +1,168 @@ +#include +#include +#include + +using namespace sidechain; + +BOOST_AUTO_TEST_SUITE( bitcoin_transaction_tests ) + +BOOST_AUTO_TEST_CASE( serialize_bitcoin_transaction_test ) +{ + out_point prevout; + prevout.hash = fc::sha256( "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" ); + prevout.n = 0; + + tx_in in; + in.prevout = prevout; + in.scriptSig = parse_hex( "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" ); + in.nSequence = 4294967294; + + tx_out out1; + out1.value = 3500000000; + out1.scriptPubKey = parse_hex( "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" ); + + tx_out out2; + out2.value = 1499996160; + out2.scriptPubKey = parse_hex( "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" ); + + bitcoin_transaction tx; + tx.nVersion = 2; + tx.vin = { in }; + tx.vout = { out1, out2 }; + tx.nLockTime = 101; + + const auto serialized = pack( tx ); + + const auto expected = parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" ); + + BOOST_CHECK_EQUAL_COLLECTIONS( serialized.cbegin(), serialized.cend(), expected.cbegin(), expected.cend() ); +} + +BOOST_AUTO_TEST_CASE( deserialize_bitcoin_transaction_test ) +{ + bitcoin_transaction tx = unpack(parse_hex( "02000000019a4b71b82745879f7a5ef5081bceb3eb53dac44ef272fcdf00fdc1bd162edf890000000048473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101feffffff0200c39dd0000000001976a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac00206859000000001976a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac65000000" ) ); + + BOOST_CHECK_EQUAL( tx.nVersion, 2 ); + BOOST_CHECK_EQUAL( tx.nLockTime, 101 ); + + BOOST_REQUIRE_EQUAL( tx.vin.size(), 1 ); + BOOST_CHECK_EQUAL( tx.vin[0].prevout.hash.str(), "89df2e16bdc1fd00dffc72f24ec4da53ebb3ce1b08f55e7a9f874527b8714b9a" ); + BOOST_CHECK_EQUAL( tx.vin[0].prevout.n, 0 ); + + BOOST_CHECK( fc::to_hex( tx.vin[0].scriptSig) == "473044022025f722981037f23949df7ac020e9895f6b4d35f98d04dadc43a8897d756b02f202203d4df0a81dac49be676657357f083d06f57b2d6b1199511ebdbd3bf82feff24101" ); + BOOST_CHECK_EQUAL( tx.vin[0].nSequence, 4294967294 ); + + BOOST_REQUIRE_EQUAL( tx.vout.size(), 2 ); + BOOST_CHECK_EQUAL( tx.vout[0].value, 3500000000 ); + BOOST_CHECK( fc::to_hex( tx.vout[0].scriptPubKey ) == "76a914d377a5fd22419f8180f6d0d12215daffdd15b80088ac" ); + BOOST_CHECK_EQUAL(tx.vout[1].value, 1499996160); + BOOST_CHECK( fc::to_hex( tx.vout[1].scriptPubKey ) == "76a914e71562730a2d7b2c2c7f2f137d6ddf80e8ee024288ac" ); +} + +BOOST_AUTO_TEST_CASE( btc_tx_methods_test ) { + const auto tx = unpack( parse_hex( "0100000000010115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f856040000002322002001d5d92effa6ffba3efa379f9830d0f75618b13393827152d26e4309000e88b1ffffffff0188b3f505000000001976a9141d7cd6c75c2e86f4cbf98eaed221b30bd9a0b92888ac02473044022038421164c6468c63dc7bf724aa9d48d8e5abe3935564d38182addf733ad4cd81022076362326b22dd7bfaf211d5b17220723659e4fe3359740ced5762d0e497b7dcc012321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac00000000" ) ); + + BOOST_CHECK( tx.get_hash().str() == "a5947589e2762107ff650958ba0e3a3cf341f53281d15593530bf9762c4edab1" ); + BOOST_CHECK( tx.get_txid().str() == "954f43dbb30ad8024981c07d1f5eb6c9fd461e2cf1760dd1283f052af746fc88" ); + BOOST_CHECK( tx.get_vsize() == 148 ); +} + +BOOST_AUTO_TEST_CASE( bitcoin_transaction_builder_test ) +{ + // All tests are only to verefy the compilation of transactions, the transactions are not real(not valid) + { // P2PKH to P2PKH + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2PKH, fc::sha256( "5d42b45d5a3ddcf2421b208885871121551acf6ea5cc1c1b4e666537ab6fcbef" ), 0, bytes() ); + tb.add_out( payment_type::P2PKH, 4999990000, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex(pack( tx ) ) == "0200000001efcb6fab3765664e1b1ccca56ecf1a552111878588201b42f2dc3d5a5db4425d0000000000ffffffff01f0ca052a010000001976a9143307bf6f98832e53a48b144d65c6a95700a93ffb88ac00000000"); + } + { // coinbase to P2PK + const auto pubkey = fc::raw::unpack( parse_hex( "02028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00" ) ); + out_point prevout; + prevout.hash = fc::sha256( "0000000000000000000000000000000000000000000000000000000000000000" ); + prevout.n = 0xffffffff; + + tx_in txin; + txin.prevout = prevout; + + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, txin, parse_hex( "022a020101" ) ); + tb.add_out( payment_type::P2PK, 625000000, pubkey); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "21030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010000000000000000000000000000000000000000000000000000000000000000ffffffff05022a020101ffffffff0240be402500000000232102028322f70f9bf4a014fb6422f555b05d605229460259c157b3fe34b7695f2d00ac0000000000000000256a2321030e7061b9fb18571cf2441b2a7ee2419933ddaa423bc178672cd11e87911616d1ac00000000" ); + } + { // P2SH to P2SH + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH, fc::sha256( "40eee3ae1760e3a8532263678cdf64569e6ad06abc133af64f735e52562bccc8" ), 0, bytes() ); + tb.add_out( payment_type::P2SH, 0xffffffff, "3P14159f73E4gFr7JterCCQh9QjiTjiZrG" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000001c8cc2b56525e734ff63a13bc6ad06a9e5664df8c67632253a8e36017aee3ee400000000000ffffffff01ffffffff0000000017a914e9c3dd0c07aac76179ebc76a6c78d4d67c6c160a8700000000" ); + } + { // P2PK to NULLDATA + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2PK, fc::sha256( "fa897a4a2b8bc507db6cf4425e81ca7ebde89a369e07d608ac7f7c311cb13b4f" ), 0, bytes() ); + tb.add_out( payment_type::NULLDATA, 0, parse_hex( "ffffffff" ) ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000014f3bb11c317c7fac08d6079e369ae8bd7eca815e42f46cdb07c58b2b4a7a89fa0000000000ffffffff010000000000000000066a04ffffffff00000000" ); + } + { // P2PK+P2PKH to P2PKH,P2PKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2PK, fc::sha256( "13d29149e08b6ca63263f3dddd303b32f5aab646ebc6b7db84756d80a227f6d9" ), 0, bytes() ); + tb.add_in( payment_type::P2PKH, fc::sha256( "a8e7f661925cdd2c0e37fc93c03540c113aa6bcea02b35de09377127f76d0da3" ), 0, bytes() ); + tb.add_out( payment_type::P2PKH, 4999990000, "mzg9RZ1p29uNXu4uTWoMdMERdVXZpunJhW" ); + tb.add_out( payment_type::P2PKH, 4999990000, "n2SPW6abRxUnnTSSHp73VGahbPW4WT9GaK" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "0200000002d9f627a2806d7584dbb7c6eb46b6aaf5323b30ddddf36332a66c8be04991d2130000000000ffffffffa30d6df727713709de352ba0ce6baa13c14035c093fc370e2cdd5c9261f6e7a80000000000ffffffff02f0ca052a010000001976a914d2276c7ed7af07f697175cc2cbcbbf32da81caba88acf0ca052a010000001976a914e57d9a9af070998bedce991c4d8e39f9c51eb93a88ac00000000" ); + } + { // P2WPKH to P2WPKH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 1, bytes() ); + tb.add_out( payment_type::P2WPKH, 99988480, "mkAn3ASzVBTLMbaLf2YTfcotdQ8hSbKD14" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560100000000ffffffff0100b4f505000000001600143307bf6f98832e53a48b144d65c6a95700a93ffb00000000" ); + } + { // P2WSH to P2WSH + bitcoin_transaction_builder tb; + tb.set_version(2); + tb.add_in( payment_type::P2WSH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115"), 2, bytes() ); + tb.add_out( payment_type::P2WSH, 99988360, "p2xtZoXeX5X8BP8JfFhQK2nD3emtjch7UeFm" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560200000000ffffffff0188b3f505000000001600140000010966776006953d5567439e5e39f86a0d2700000000" ); + } + { // P2SH(WPKH) to P2SH(WPKH) + bitcoin_transaction_builder tb; + tb.set_version( 2 ); + tb.add_in( payment_type::P2SH_WPKH, fc::sha256( "56f87210814c8baef7068454e517a70da2f2103fc3ac7f687e32a228dc80e115" ), 3, parse_hex( "ab68025513c3dbd2f7b92a94e0581f5d50f654e7" ) ); + tb.add_out( payment_type::P2SH_WPKH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "020000000115e180dc28a2327e687facc33f10f2a20da717e5548406f7ae8b4c811072f8560300000014ab68025513c3dbd2f7b92a94e0581f5d50f654e7ffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" ); + } + { // P2SH(WSH) to P2SH(WSH) + bitcoin_transaction_builder tb; + tb.set_version(2); + const auto redeem_script = parse_hex( "21038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acac"); + tb.add_in( payment_type::P2SH_WSH, fc::sha256( "fca01bd539623013f6f945dc6173c395394621ffaa53a9eb6da6e9a2e7c9400e" ), 0, redeem_script ); + tb.add_out( payment_type::P2SH_WSH, 99987100, "3Mwz6cg8Fz81B7ukexK8u8EVAW2yymgWNd" ); + + const auto tx = tb.get_transaction(); + BOOST_CHECK( fc::to_hex( pack( tx ) ) == "02000000010e40c9e7a2e9a66deba953aaff21463995c37361dc45f9f613306239d51ba0fc000000002321038262a6c6cec93c2d3ecd6c6072efea86d02ff8e3328bbd0242b20af3425990acacffffffff019caef5050000000017a914de373b053abb48ec078cf5f41b42aedac0103e278700000000" ); + } +} + +BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/tests/input_withdrawal_info_tests.cpp b/tests/tests/input_withdrawal_info_tests.cpp index 9bca1ea9..38f087d2 100644 --- a/tests/tests/input_withdrawal_info_tests.cpp +++ b/tests/tests/input_withdrawal_info_tests.cpp @@ -154,7 +154,7 @@ BOOST_AUTO_TEST_CASE( input_withdrawal_info_get_info_for_vins_test ) BOOST_AUTO_TEST_CASE( input_withdrawal_info_insert_vout_test ) { input_withdrawal_info infos( db ); - infos.insert_info_for_vout( account_id_type(), payment_type::NULLDATA, "1", 1 ); + infos.insert_info_for_vout( account_id_type(), "1", 1 ); BOOST_CHECK( infos.size_info_for_vouts() == 1 ); } @@ -162,7 +162,7 @@ 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), payment_type::NULLDATA, std::to_string( i ), static_cast( i ) ); + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); } BOOST_CHECK( infos.size_info_for_vouts() == 10 ); } @@ -171,7 +171,7 @@ 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), payment_type::NULLDATA, std::to_string( i ), static_cast( i ) ); + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); } for( size_t i = 0; i < 10; i++ ) { @@ -195,7 +195,7 @@ 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), payment_type::NULLDATA, std::to_string( i ), static_cast( i ) ); + infos.insert_info_for_vout( account_id_type(i), std::to_string( i ), static_cast( i ) ); } const auto& vouts = infos.get_info_for_vouts(); diff --git a/tests/tests/sidechain_condensing_tx_tests.cpp b/tests/tests/sidechain_condensing_tx_tests.cpp new file mode 100644 index 00000000..ad0ee5be --- /dev/null +++ b/tests/tests/sidechain_condensing_tx_tests.cpp @@ -0,0 +1,254 @@ +#include +#include +#include +#include +#include + +#include +#include + +BOOST_AUTO_TEST_SUITE( sidechain_condensing_tx_tests ) + +uint64_t size_fee = 100; +uint64_t pw_vout_amount = 113; +double witness_percentage = 0.01; + +void create_info_vins_and_info_vouts( std::vector& info_vins, std::vector& info_vouts ) +{ + for( size_t i = 0; i < 10; i++ ) { + info_for_vin vin; + vin.out.hash_tx = "1111111111111111111111111111111111111111111111111111111111111111"; + vin.out.n_vout = static_cast( i ); + vin.out.amount = static_cast( i + 1000 ); + vin.address = std::to_string( i ); + vin.script = { 0x0d }; + info_vins.push_back( vin ); + + info_for_vout vout; + vout.payer = account_id_type( i ); + vout.address = sidechain::bitcoin_address( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + vout.amount = static_cast( i + 1000 ); + info_vouts.push_back( vout ); + } +} + +info_for_vin create_pw_vin() +{ + info_for_vin vin_info; + vin_info.out.hash_tx = "2222222222222222222222222222222222222222222222222222222222222222"; + vin_info.out.n_vout = static_cast( 13 ); + vin_info.out.amount = static_cast( 1113 ); + vin_info.address = std::to_string( 13 ); + vin_info.script = { 0x0d }; + + return vin_info; +} + +accounts_keys create_accounts_keys() +{ + fc::ecc::private_key priv_key = fc::ecc::private_key::regenerate( fc::digest( "key" ) ); + return { { account_id_type( 0 ), public_key_type( priv_key.get_public_key() ) }, + { account_id_type( 1 ), public_key_type( priv_key.get_public_key() ) }, + { account_id_type( 2 ), public_key_type( priv_key.get_public_key() ) } }; +} + +BOOST_AUTO_TEST_CASE( create_sidechain_condensing_tx_test ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + const auto& tx = ct.get_transaction(); + + BOOST_CHECK( tx.vin.size() == 10 ); + BOOST_CHECK( tx.vout.size() == 10 ); + for( size_t i = 0; i < 10; i++ ) { + BOOST_CHECK( tx.vin[i].prevout.hash == fc::sha256( "1111111111111111111111111111111111111111111111111111111111111111" ) ); + BOOST_CHECK( tx.vin[i].prevout.n == static_cast( i ) ); + bytes scriptSig = { 0x22, 0x0d }; + BOOST_CHECK( tx.vin[i].scriptSig == scriptSig ); + BOOST_CHECK( tx.vin[i].scriptWitness == std::vector() ); + + BOOST_CHECK( tx.vout[i].value == static_cast( i + 1000 ) ); + const auto address_bytes = fc::from_base58( "mipcBbFg9gMiCh81Kj8tqqdgoZub1ZJRfn" ); + bytes raw_address( address_bytes.begin() + 1, address_bytes.begin() + 21 ); + bytes scriptPubKey = script_builder() << op::DUP << op::HASH160 << raw_address << op::EQUALVERIFY << op::CHECKSIG; + BOOST_CHECK( tx.vout[i].scriptPubKey == scriptPubKey ); + } +} + +BOOST_AUTO_TEST_CASE( creation_additional_in_out_sidechain_condensing_tx ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( 1113, bytes{ 0x0d, 0x0d, 0x0d } ); + const auto& tx = ct.get_transaction(); + + BOOST_CHECK( tx.vin.size() == 11 ); + BOOST_CHECK( tx.vout.size() == 14 ); + + BOOST_CHECK( tx.vin[0].prevout.hash == fc::sha256( "2222222222222222222222222222222222222222222222222222222222222222" ) ); + BOOST_CHECK( tx.vin[0].prevout.n == static_cast( 13 ) ); + bytes scriptSig = { 0x22, 0x0d }; + BOOST_CHECK( tx.vin[0].scriptSig == bytes() ); + + BOOST_CHECK( tx.vout[0].value == static_cast( 1113 ) ); + bytes scriptPubKey{ 0x00, 0x01, 0x0d }; + BOOST_CHECK( tx.vout[0].scriptPubKey == scriptPubKey ); + for( size_t i = 1; i <= keys.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( 0 ) ); + bytes scriptPubKey{ 0x21 }; + const auto key_data = keys[account_id_type( i - 1 )].key_data; + std::copy( key_data.begin(), key_data.end(), std::back_inserter( scriptPubKey ) ); + scriptPubKey.push_back( 0xac ); + BOOST_CHECK( tx.vout[i].scriptPubKey == scriptPubKey ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + accounts_keys keys = create_accounts_keys(); + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage ); + } + + uint64_t witnesses_fee_sum = std::accumulate( witnesses_fee.begin(), witnesses_fee.end(), 0 ); + uint64_t witness_fee = witnesses_fee_sum / keys.size(); + BOOST_CHECK( tx.vout[0].value == static_cast( pw_vout_amount ) ); + for( size_t i = 1; i <= keys.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( witness_fee ) ); + } + + size_t offset = 1 + keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_pw_vout_and_witness_vouts_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage ); + } + + for( size_t i = 0; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i].amount - size_fee_user ) - witnesses_fee[i] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_witness_vouts_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage ); + } + + size_t offset = 1; + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_pw_vout_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage ); + } + + size_t offset = keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_CASE( subtract_fee_not_vins_vout_tests ) +{ + std::vector info_vins; + std::vector info_vouts; + + create_info_vins_and_info_vouts( info_vins, info_vouts ); + info_vins.clear(); + uint64_t size_fee_user = size_fee / ( info_vins.size() + info_vouts.size() ); + accounts_keys keys = create_accounts_keys(); + + sidechain_condensing_tx ct( info_vins, info_vouts ); + ct.create_pw_vin( create_pw_vin() ); + ct.create_vouts_for_witness_fee( keys ); + ct.create_pw_vout( pw_vout_amount, bytes{ 0x0d, 0x0d, 0x0d } ); + ct.subtract_fee( size_fee, witness_percentage ); + const auto& tx = ct.get_transaction(); + + std::vector witnesses_fee; + for( size_t i = 0; i < info_vouts.size(); i++ ) { + witnesses_fee.push_back( ( info_vouts[i].amount - size_fee_user ) * witness_percentage ); + } + + size_t offset = 1 + keys.size(); + for( size_t i = offset; i < tx.vout.size(); i++ ) { + BOOST_CHECK( tx.vout[i].value == static_cast( ( info_vouts[i - offset].amount - size_fee_user ) - witnesses_fee[i - offset] ) ); + } +} + +BOOST_AUTO_TEST_SUITE_END()