diff --git a/libraries/plugins/peerplays_sidechain/bitcoin_transaction.cpp b/libraries/plugins/peerplays_sidechain/bitcoin_transaction.cpp new file mode 100644 index 00000000..e099cd3f --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/bitcoin_transaction.cpp @@ -0,0 +1,279 @@ +#include +#include +#include +#include + +namespace sidechain { + +bool out_point::operator==( const out_point& op ) const +{ + if( this->hash == op.hash && + this->n == op.n ) + { + return true; + } + return false; +} + +bool tx_in::operator==( const tx_in& ti ) const +{ + if( this->prevout == ti.prevout && + this->scriptSig == ti.scriptSig && + this->nSequence == ti.nSequence ) + { + return true; + } + return false; +} + +bool tx_out::operator==( const tx_out& to ) const +{ + if( this->value == to.value && + this->scriptPubKey == to.scriptPubKey ) + { + return true; + } + return false; +} + +bool tx_out::is_p2wsh() const +{ + if( scriptPubKey.size() == 34 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x20) ) { + return true; + } + return false; +} + +bool tx_out::is_p2wpkh() const +{ + if( scriptPubKey.size() == 22 && scriptPubKey[0] == static_cast(0x00) && scriptPubKey[1] == static_cast(0x14) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pkh() const +{ + if( scriptPubKey.size() == 25 && scriptPubKey[0] == static_cast(0x76) && scriptPubKey[1] == static_cast(0xa9) && + scriptPubKey[2] == static_cast(0x14) && scriptPubKey[23] == static_cast(0x88) && scriptPubKey[24] == static_cast(0xac) ) { + return true; + } + return false; +} + +bool tx_out::is_p2sh() const +{ + if( scriptPubKey.size() == 23 && scriptPubKey[0] == static_cast(0xa9) && + scriptPubKey[1] == static_cast(0x14) && scriptPubKey[22] == static_cast(0x87) ) { + return true; + } + return false; +} + +bool tx_out::is_p2pk() const +{ + if( scriptPubKey.size() == 35 && scriptPubKey[0] == static_cast(0x21) && scriptPubKey[34] == static_cast(0xac) ) { + return true; + } + return false; +} + +bytes tx_out::get_data_or_script() const +{ + if( is_p2pkh() ) { + return bytes( scriptPubKey.begin() + 3, scriptPubKey.begin() + 23 ); + } else if( is_p2sh() || is_p2wpkh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 22 ); + } else if( is_p2wsh() ) { + return bytes( scriptPubKey.begin() + 2, scriptPubKey.begin() + 34 ); + } else if( is_p2pk() ) { + return bytes( scriptPubKey.begin() + 1, scriptPubKey.begin() + 34 ); + } + return scriptPubKey; +} + +bool bitcoin_transaction::operator!=( const bitcoin_transaction& bt ) const +{ + if( this->nVersion != bt.nVersion || + this->vin != bt.vin || + this->vout != bt.vout || + this->nLockTime != bt.nLockTime ) + { + return true; + } + return false; +} + +fc::sha256 bitcoin_transaction::get_hash() const +{ + const auto bytes = pack( *this, true) ; + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +fc::sha256 bitcoin_transaction::get_txid() const +{ + const auto bytes = pack( *this, false ); + const auto hash = fc::sha256::hash( fc::sha256::hash(bytes.data(), bytes.size()) ); + std::reverse( hash.data(), hash.data() + hash.data_size() ); + return hash; +} + +size_t bitcoin_transaction::get_vsize() const +{ + static const auto witness_scale_factor = 4; + + fc::datastream no_wit_ds; + pack( no_wit_ds, *this, false ); + + fc::datastream wit_ds; + pack( wit_ds, *this, true ); + + const size_t weight = no_wit_ds.tellp() * ( witness_scale_factor - 1 ) + wit_ds.tellp(); + const size_t vsize = ( weight + witness_scale_factor - 1 ) / witness_scale_factor; + + return vsize; +} + +void bitcoin_transaction_builder::set_version( int32_t version ) +{ + tx.nVersion = version; +} + +void bitcoin_transaction_builder::set_locktime(uint32_t lock_time) +{ + tx.nLockTime = lock_time; +} + +void bitcoin_transaction_builder::add_in( payment_type type, const fc::sha256& txid, uint32_t n_out, const bytes& script_code, bool front, uint32_t sequence ) +{ + out_point prevout; + prevout.hash = txid; + prevout.n = n_out; + + tx_in txin; + txin.prevout = prevout; + txin.nSequence = sequence; + + add_in( type, txin, script_code, front ); +} + +void bitcoin_transaction_builder::add_in( payment_type type, tx_in txin, const bytes& script_code, bool front ) +{ + switch ( type ) { + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + break; + default:{ + if( txin.prevout.hash == fc::sha256("0000000000000000000000000000000000000000000000000000000000000000") ) { //coinbase + FC_ASSERT( script_code != bytes() ); + txin.scriptSig = script_code; + } + break; + } + } + + if( front ) { + tx.vin.insert( tx.vin.begin(), txin ); + } else { + tx.vin.push_back( txin ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const std::string& base58_address, bool front ) +{ + // TODO: add checks + const auto address_bytes = fc::from_base58(base58_address); + add_out( type, amount, bytes( address_bytes.begin() + 1, address_bytes.begin() + 1 + 20 ), front ); +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const fc::ecc::public_key_data& pubkey, bool front ) +{ + FC_ASSERT( is_payment_to_pubkey( type ) ); + + if ( type == payment_type::P2PK ) { + const auto pubkey_bytes = bytes( pubkey.begin(), pubkey.begin() + pubkey.size() ); + add_out( type, amount, pubkey_bytes, front ); + } else { + const auto hash256 = fc::sha256::hash( pubkey.begin(), pubkey.size() ); + const auto hash160 = fc::ripemd160::hash( hash256.data(), hash256.data_size() ); + add_out( type, amount, bytes( hash160.data(), hash160.data() + hash160.data_size() ), front ); + } +} + +void bitcoin_transaction_builder::add_out( payment_type type, int64_t amount, const bytes& script_code, bool front ) +{ + tx_out out; + out.value = amount; + out.scriptPubKey = get_script_pubkey( type, script_code ); + + if( front ) { + tx.vout.insert( tx.vout.begin(), out ); + } else { + tx.vout.push_back( out ); + } +} + +void bitcoin_transaction_builder::add_out_all_type( const uint64_t& amount, const bitcoin_address& address, bool front ) +{ + switch( address.get_type() ) { + case payment_type::P2PK: { + bytes raw_address( address.get_raw_address() ); + fc::ecc::public_key_data public_key; + std::copy( raw_address.begin(), raw_address.end(), public_key.begin() ); + add_out( address.get_type(), amount, public_key, front ); + break; + } + case payment_type::P2PKH: + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: { + add_out( address.get_type(), amount, address.get_address(), front ); + break; + } + case payment_type::P2WPKH: + case payment_type::P2WSH: { + add_out( address.get_type(), amount, address.get_raw_address(), front ); + break; + } + } +} + +inline bool bitcoin_transaction_builder::is_payment_to_pubkey( payment_type type ) +{ + switch ( type ) { + case payment_type::P2PK: + case payment_type::P2PKH: + case payment_type::P2WPKH: + case payment_type::P2SH_WPKH: + return true; + default: + return false; + } +} + +bytes bitcoin_transaction_builder::get_script_pubkey( payment_type type, const bytes& script_code ) +{ + switch ( type ) { + case payment_type::NULLDATA: + return script_builder() << op::RETURN << script_code; + case payment_type::P2PK: + return script_builder() << script_code << op::CHECKSIG; + case payment_type::P2PKH: + return script_builder() << op::DUP << op::HASH160 << script_code << op::EQUALVERIFY << op::CHECKSIG; + case payment_type::P2SH: + case payment_type::P2SH_WPKH: + case payment_type::P2SH_WSH: + return script_builder() << op::HASH160 << script_code << op::EQUAL; + case payment_type::P2WPKH: + case payment_type::P2WSH: + return script_builder() << op::_0 << script_code; + default: + return script_code; + } +} + +} diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_address.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_address.hpp new file mode 100644 index 00000000..e55dc87b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_address.hpp @@ -0,0 +1,26 @@ +#pragma once +#include + +namespace graphene { namespace chain { + + struct bitcoin_address_create_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 20 * GRAPHENE_BLOCKCHAIN_PRECISION; + }; + + asset fee; + account_id_type payer; + account_id_type owner; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + return k.fee; + } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_address_create_operation::fee_parameters_type, (fee) ) +FC_REFLECT( graphene::chain::bitcoin_address_create_operation, (fee)(payer)(owner) ) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_transaction.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_transaction.hpp new file mode 100644 index 00000000..5ec8d9ac --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/bitcoin_transaction.hpp @@ -0,0 +1,97 @@ +#pragma once +#include +#include +#include + +namespace graphene { namespace chain { + + struct bitcoin_transaction_send_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + sidechain::info_for_vin pw_vin; + std::vector< sidechain::info_for_vin > vins; + std::vector< info_for_vout_id_type > vouts; + + sidechain::bitcoin_transaction transaction; + uint64_t fee_for_size; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { + share_type fee_required = k.fee; + fee_required += calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte ); + return fee_required; + } + }; + + struct bitcoin_transaction_sign_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + proposal_id_type proposal_id; + std::vector signatures; + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return 0; } + }; + + struct revert_trx_info + { + revert_trx_info() = default; + revert_trx_info( fc::sha256 trx_id, std::set< fc::sha256 > vins ): transaction_id( trx_id ), valid_vins( vins ) {} + + fc::sha256 transaction_id; + std::set< fc::sha256 > valid_vins; + + bool operator ==( const revert_trx_info& trx_info ){ + return this->transaction_id == trx_info.transaction_id && this->valid_vins == trx_info.valid_vins; + } + + bool operator !=( const revert_trx_info& trx_info ){ + return this->transaction_id != trx_info.transaction_id || this->valid_vins != trx_info.valid_vins; + } + }; + + struct bitcoin_transaction_revert_operation : public base_operation + { + struct fee_parameters_type { + uint64_t fee = 0; + uint32_t price_per_kbyte = 0; + }; + + asset fee; + account_id_type payer; + + std::vector< revert_trx_info > transactions_info; + + + account_id_type fee_payer()const { return payer; } + void validate()const {} + share_type calculate_fee( const fee_parameters_type& k )const { return k.fee; } + }; + +} } // graphene::chain + +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(pw_vin)(vins)(vouts)(transaction)(fee_for_size) ) + +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signatures) ) + +FC_REFLECT( graphene::chain::revert_trx_info, (transaction_id)(valid_vins) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation::fee_parameters_type, (fee)(price_per_kbyte) ) +FC_REFLECT( graphene::chain::bitcoin_transaction_revert_operation, (fee)(payer)(transactions_info) ) + diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/info_for_vout_object.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/info_for_vout_object.hpp new file mode 100644 index 00000000..fcc06afe --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/info_for_vout_object.hpp @@ -0,0 +1,54 @@ +#pragma once + +#include +#include +#include +#include + +namespace graphene { namespace chain { + +class info_for_vout_object : public abstract_object +{ + public: + static const uint8_t space_id = protocol_ids; + static const uint8_t type_id = info_for_vout_object_type; + + struct comparer { + bool operator()( const info_for_vout_object& lhs, const info_for_vout_object& rhs ) const { + if( lhs.used != rhs.used ) + return lhs.used < rhs.used; + return lhs.id < rhs.id; + } + }; + + bool operator==( const info_for_vout_object& ifv ) const { + return ( this->payer == ifv.payer ) && + ( this->address == ifv.address ) && + ( this->amount == ifv.amount ); + } + + info_for_vout_id_type get_id()const { return id; } + + account_id_type payer; + sidechain::bitcoin_address address; + uint64_t amount; + + bool used = false; +}; + +struct by_created; +struct by_id_and_not_used; + +typedef boost::multi_index_container< + info_for_vout_object, + indexed_by< + ordered_unique< tag< by_id >, member< object, object_id_type, &object::id > >, + ordered_non_unique< tag< by_created >, member< info_for_vout_object, bool, &info_for_vout_object::used > >, + ordered_non_unique, identity< info_for_vout_object >, info_for_vout_object::comparer > + > +> info_for_vout_multi_index_container; +typedef generic_index info_for_vout_index; + +} } // graphene::chain + +FC_REFLECT_DERIVED( graphene::chain::info_for_vout_object, (graphene::chain::object), (payer)(address)(amount)(used) ) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/input_withdrawal_info.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/input_withdrawal_info.hpp new file mode 100644 index 00000000..208aa21b --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/input_withdrawal_info.hpp @@ -0,0 +1,113 @@ +#pragma once + +#include +#include +#include + +#include +#include +#include +#include +#include + +using boost::multi_index_container; +using namespace boost::multi_index; + +namespace graphene { namespace chain { class database; } } + +namespace sidechain { + +struct info_for_vin +{ + info_for_vin() = default; + + info_for_vin( const prev_out& _out, const std::string& _address, bytes _script = bytes(), bool _resend = false ); + + bool operator!=( const info_for_vin& obj ) const; + + struct comparer { + bool operator() ( const info_for_vin& lhs, const info_for_vin& rhs ) const; + }; + + static uint64_t count_id_info_for_vin; + uint64_t id; + + fc::sha256 identifier; + + prev_out out; + std::string address; + bytes script; + + bool used = false; + bool resend = false; +}; + +struct by_id; +struct by_identifier; +struct by_id_resend_not_used; + +using info_for_vin_index = boost::multi_index_container, member>, + ordered_unique, member>, + ordered_non_unique, identity< info_for_vin >, info_for_vin::comparer > + > +>; + +class input_withdrawal_info +{ + +public: + input_withdrawal_info( graphene::chain::database& _db ) : db( _db ) {} + + + std::vector get_redeem_scripts( const std::vector& info_vins ); + + std::vector get_amounts( const std::vector& info_vins ); + + + fc::optional get_info_for_pw_vin(); + + + void insert_info_for_vin( const prev_out& out, const std::string& address, bytes script = bytes(), bool resend = false ); + + void modify_info_for_vin( const info_for_vin& obj, const std::function& func ); + + void mark_as_used_vin( const info_for_vin& obj ); + + void mark_as_unused_vin( const info_for_vin& obj ); + + void remove_info_for_vin( const info_for_vin& obj ); + + fc::optional find_info_for_vin( fc::sha256 identifier ); + + size_t size_info_for_vins() { return info_for_vins.size(); } + + std::vector get_info_for_vins(); + + + void insert_info_for_vout( const graphene::chain::account_id_type& payer, const std::string& data, const uint64_t& amount ); + + void mark_as_used_vout( const graphene::chain::info_for_vout_object& obj ); + + void mark_as_unused_vout( const graphene::chain::info_for_vout_object& obj ); + + void remove_info_for_vout( const info_for_vout& obj ); + + fc::optional find_info_for_vout( graphene::chain::info_for_vout_id_type id ); + + size_t size_info_for_vouts(); + + std::vector get_info_for_vouts(); + +private: + + graphene::chain::database& db; + + thread_safe_index info_for_vins; + +}; + +} + +FC_REFLECT( sidechain::info_for_vin, (identifier)(out)(address)(script)(used)(resend) ) diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_network_manager.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_network_manager.hpp index 4afd3f87..5c8604a1 100644 --- a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_network_manager.hpp +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/sidechain_network_manager.hpp @@ -1,6 +1,7 @@ #pragma once #include +#include #include #include diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/thread_safe_index.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/thread_safe_index.hpp new file mode 100644 index 00000000..c5508a38 --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/thread_safe_index.hpp @@ -0,0 +1,82 @@ +#pragma once + +#include +#include + +namespace sidechain { + +template +class thread_safe_index +{ + +public: + + using iterator = typename T1::iterator; + + std::pair insert( const typename T1::value_type& value ) { + std::lock_guard locker( lock ); + return data.insert( value ); + } + + template + bool modify( const Key _key, const std::function& func ) { + std::lock_guard locker( lock ); + const auto option = find_iterator( _key ); + if( !option ) + return false; + data.modify( data.iterator_to( **option ), [&func]( typename T1::value_type& obj ) { func( obj ); } ); + return true; + } + + template + bool remove( const Key _key ) { + std::lock_guard locker( lock ); + const auto option = find_iterator( _key ); + if( !option ) + return false; + data.erase( data.iterator_to( **option ) ); + return true; + } + + size_t size() { + std::lock_guard locker( lock ); + return data.size(); + } + + template + fc::optional find( const Key _key ) { + std::lock_guard locker( lock ); + auto& index = data.template get(); + auto it = index.find( _key ); + if( it != index.end() ) { + return fc::optional(*it); + } + return fc::optional(); + } + + template + void safe_for(std::function::type::iterator itr1, + typename T1::template index::type::iterator itr2)> func) { + std::lock_guard locker( lock ); + auto& index = data.template get(); + func(index.begin(), index.end()); + } + +private: + + template + fc::optional::type::iterator> find_iterator( const Key _key ) { + auto& index = data.template get(); + auto it = index.find( _key ); + if( it == index.end() ) + return fc::optional::type::iterator>(); + return it; + } + + std::recursive_mutex lock; + + T1 data; + +}; + +} diff --git a/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/types.hpp b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/types.hpp new file mode 100644 index 00000000..bbe3ec4c --- /dev/null +++ b/libraries/plugins/peerplays_sidechain/include/graphene/peerplays_sidechain/types.hpp @@ -0,0 +1,60 @@ +#pragma once + +#include +#include +#include +#include + +#include + +namespace sidechain { + +class bitcoin_transaction; + +using bytes = std::vector; +using accounts_keys = std::map< graphene::chain::account_id_type, graphene::chain::public_key_type >; +using info_for_vout = graphene::chain::info_for_vout_object; +using full_btc_transaction = std::pair; + +enum class payment_type +{ + NULLDATA, + P2PK, + P2PKH, + P2SH, + P2WPKH, + P2WSH, + P2SH_WPKH, + P2SH_WSH +}; + +enum class sidechain_proposal_type +{ + ISSUE_BTC, + SEND_BTC_TRANSACTION, + REVERT_BTC_TRANSACTION +}; + +struct prev_out +{ + bool operator!=( const prev_out& obj ) const + { + if( this->hash_tx != obj.hash_tx || + this->n_vout != obj.n_vout || + this->amount != obj.amount ) + { + return true; + } + return false; + } + + std::string hash_tx; + uint32_t n_vout; + uint64_t amount; +}; + +} + +FC_REFLECT_ENUM( sidechain::payment_type, (NULLDATA)(P2PK)(P2PKH)(P2SH)(P2WPKH)(P2WSH)(P2SH_WPKH)(P2SH_WSH) ); +FC_REFLECT_ENUM( sidechain::sidechain_proposal_type, (ISSUE_BTC)(SEND_BTC_TRANSACTION)(REVERT_BTC_TRANSACTION) ); +FC_REFLECT( sidechain::prev_out, (hash_tx)(n_vout)(amount) ); diff --git a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp index 50c21d2d..d94b0e0a 100644 --- a/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp +++ b/libraries/plugins/peerplays_sidechain/peerplays_sidechain_plugin.cpp @@ -2,6 +2,8 @@ #include +namespace bpo = boost::program_options; + namespace graphene { namespace peerplays_sidechain { namespace detail