SON261 - Merge with latest sons base and addition of peerplays sig collection

This commit is contained in:
Koneru Satyanarayana 2020-02-20 01:59:15 +11:00
commit 1d304993e9
19 changed files with 1695 additions and 138 deletions

View file

@ -337,6 +337,15 @@ struct get_impacted_account_visitor
void operator()( const sidechain_address_delete_operation& op ){
_impacted.insert( op.sidechain_address_account );
}
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_send_transaction_process_operation& op ){
_impacted.insert( op.payer );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )

View file

@ -121,6 +121,7 @@ add_library( graphene_chain
son_wallet_transfer_evaluator.cpp
sidechain_address_evaluator.cpp
sidechain_transaction_evaluator.cpp
${HEADERS}
${PROTOCOL_HEADERS}

View file

@ -59,6 +59,7 @@
#include <graphene/chain/son_wallet_object.hpp>
#include <graphene/chain/son_wallet_transfer_object.hpp>
#include <graphene/chain/sidechain_address_object.hpp>
#include <graphene/chain/sidechain_transaction_object.hpp>
#include <graphene/chain/account_evaluator.hpp>
#include <graphene/chain/asset_evaluator.hpp>
@ -84,6 +85,7 @@
#include <graphene/chain/son_wallet_evaluator.hpp>
#include <graphene/chain/son_wallet_transfer_evaluator.hpp>
#include <graphene/chain/sidechain_address_evaluator.hpp>
#include <graphene/chain/sidechain_transaction_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -263,6 +265,9 @@ void database::initialize_evaluators()
register_evaluator<add_sidechain_address_evaluator>();
register_evaluator<update_sidechain_address_evaluator>();
register_evaluator<delete_sidechain_address_evaluator>();
register_evaluator<bitcoin_transaction_send_evaluator>();
register_evaluator<bitcoin_transaction_sign_evaluator>();
register_evaluator<bitcoin_send_transaction_process_evaluator>();
}
void database::initialize_indexes()
@ -311,6 +316,7 @@ void database::initialize_indexes()
add_index< primary_index<son_wallet_transfer_index> >();
add_index< primary_index<sidechain_address_index> >();
add_index< primary_index<bitcoin_transaction_index> >();
//Implementation object indexes
add_index< primary_index<transaction_index > >();

View file

@ -324,6 +324,15 @@ struct get_impacted_account_visitor
void operator()( const sidechain_address_delete_operation& op ) {
_impacted.insert( op.sidechain_address_account );
}
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_send_transaction_process_operation& op ) {
_impacted.insert( op.payer );
}
};
void operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )

View file

@ -49,6 +49,7 @@
#include <graphene/chain/protocol/sidechain_address.hpp>
#include <graphene/chain/protocol/son_wallet.hpp>
#include <graphene/chain/protocol/son_wallet_transfer.hpp>
#include <graphene/chain/protocol/sidechain_transaction.hpp>
namespace graphene { namespace chain {
@ -152,7 +153,10 @@ namespace graphene { namespace chain {
son_wallet_transfer_process_operation,
sidechain_address_add_operation,
sidechain_address_update_operation,
sidechain_address_delete_operation
sidechain_address_delete_operation,
bitcoin_transaction_send_operation,
bitcoin_transaction_sign_operation,
bitcoin_send_transaction_process_operation
> operation;
/// @} // operations group

View file

@ -0,0 +1,62 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/types.hpp>
#include <graphene/peerplays_sidechain/defs.hpp>
namespace graphene { namespace chain {
struct bitcoin_transaction_send_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
account_id_type payer;
// TODO: BTC Transaction Structs go here
std::string tx_hex;
fc::flat_map<son_id_type, std::vector<peerplays_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 bitcoin_transaction_sign_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
account_id_type payer;
proposal_id_type proposal_id;
std::string signed_tx_hex;
std::vector<peerplays_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 bitcoin_send_transaction_process_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = 0; };
asset fee;
account_id_type payer;
bitcoin_transaction_id_type bitcoin_transaction_id;
account_id_type fee_payer()const { return payer; }
void validate()const {}
share_type calculate_fee( const fee_parameters_type& k )const { return 0; }
};
} } // graphene::chain
FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bitcoin_transaction_send_operation, (fee)(payer)(tx_hex)(signatures) )
FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bitcoin_transaction_sign_operation, (fee)(payer)(proposal_id)(signed_tx_hex)(signatures) )
FC_REFLECT( graphene::chain::bitcoin_send_transaction_process_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::bitcoin_send_transaction_process_operation, (fee)(payer)(bitcoin_transaction_id) )

View file

@ -150,6 +150,7 @@ namespace graphene { namespace chain {
son_wallet_object_type,
son_wallet_transfer_object_type,
sidechain_address_object_type,
bitcoin_transaction_object_type,
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
};
@ -216,6 +217,7 @@ namespace graphene { namespace chain {
class son_wallet_object;
class son_wallet_transfer_object;
class sidechain_address_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;
@ -247,6 +249,7 @@ namespace graphene { namespace chain {
typedef object_id< protocol_ids, son_wallet_object_type, son_wallet_object> son_wallet_id_type;
typedef object_id< protocol_ids, son_wallet_transfer_object_type, son_wallet_transfer_object> son_wallet_transfer_id_type;
typedef object_id< protocol_ids, sidechain_address_object_type, sidechain_address_object> sidechain_address_id_type;
typedef object_id< protocol_ids, bitcoin_transaction_object_type,bitcoin_transaction_object> bitcoin_transaction_id_type;
// implementation types
class global_property_object;
@ -436,6 +439,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(son_wallet_object_type)
(son_wallet_transfer_object_type)
(sidechain_address_object_type)
(bitcoin_transaction_object_type)
(OBJECT_TYPE_COUNT)
)
FC_REFLECT_ENUM( graphene::chain::impl_object_type,
@ -512,6 +516,7 @@ FC_REFLECT_TYPENAME( graphene::chain::son_proposal_id_type )
FC_REFLECT_TYPENAME( graphene::chain::son_wallet_id_type )
FC_REFLECT_TYPENAME( graphene::chain::son_wallet_transfer_id_type )
FC_REFLECT_TYPENAME( graphene::chain::sidechain_address_id_type )
FC_REFLECT_TYPENAME( graphene::chain::bitcoin_transaction_id_type )
FC_REFLECT( graphene::chain::void_t, )

View file

@ -0,0 +1,35 @@
#pragma once
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/protocol/sidechain_transaction.hpp>
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& o);
object_id_type do_apply(const bitcoin_transaction_send_operation& o);
};
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& o);
object_id_type do_apply(const bitcoin_transaction_sign_operation& o);
void update_proposal( const bitcoin_transaction_sign_operation& o );
};
class bitcoin_send_transaction_process_evaluator : public evaluator<bitcoin_send_transaction_process_evaluator>
{
public:
typedef bitcoin_send_transaction_process_operation operation_type;
void_result do_evaluate(const bitcoin_send_transaction_process_operation& o);
object_id_type do_apply(const bitcoin_send_transaction_process_operation& o);
};
} } // namespace graphene::chain

View file

@ -0,0 +1,40 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/peerplays_sidechain/defs.hpp>
namespace graphene { namespace chain {
using namespace graphene::db;
/**
* @class bitcoin_transaction_object
* @brief tracks state of bitcoin transaction.
* @ingroup object
*/
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 structs go here.
bool processed = false;
fc::flat_map<son_id_type, std::string> tx_signatures;
fc::flat_map<son_id_type, std::vector<peerplays_sidechain::bytes>> signatures;
};
struct by_processed;
using bitcoin_transaction_multi_index_type = multi_index_container<
bitcoin_transaction_object,
indexed_by<
ordered_unique< tag<by_id>,
member<object, object_id_type, &object::id>
>,
ordered_non_unique< tag<by_processed>,
member<bitcoin_transaction_object, bool, &bitcoin_transaction_object::processed>
>
>
>;
using bitcoin_transaction_index = generic_index<bitcoin_transaction_object, bitcoin_transaction_multi_index_type>;
} } // graphene::chain
FC_REFLECT_DERIVED( graphene::chain::bitcoin_transaction_object, (graphene::db::object),
(processed)(tx_signatures)(signatures) )

View file

@ -0,0 +1,136 @@
#include <graphene/chain/sidechain_transaction_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/sidechain_transaction_object.hpp>
#include <graphene/chain/son_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/hardfork.hpp>
namespace graphene
{
namespace chain
{
void_result bitcoin_transaction_send_evaluator::do_evaluate(const bitcoin_transaction_send_operation &op)
{
try
{
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass
FC_ASSERT(op.payer == db().get_global_properties().parameters.get_son_btc_account_id(), "Payer should be the son btc account");
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
object_id_type bitcoin_transaction_send_evaluator::do_apply(const bitcoin_transaction_send_operation &op)
{
try
{
const auto &new_bitcoin_transaction_object = db().create<bitcoin_transaction_object>([&](bitcoin_transaction_object &obj) {
obj.processed = false;
obj.signatures = op.signatures;
});
return new_bitcoin_transaction_object.id;
}
FC_CAPTURE_AND_RETHROW((op))
}
void_result bitcoin_transaction_sign_evaluator::do_evaluate(const bitcoin_transaction_sign_operation &op)
{
try
{
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass
const auto &proposal_idx = db().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");
// Checks can this SON approve this proposal
auto can_this_son_approve_this_proposal = [&]() {
const auto &sidx = db().get_index_type<son_index>().indices().get<graphene::chain::by_account>();
auto son_obj = sidx.find(op.payer);
if (son_obj == sidx.end())
{
return false;
}
// TODO: Check if the SON is included in the PW script.
return true;
};
FC_ASSERT(can_this_son_approve_this_proposal(), "Invalid approval received");
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
object_id_type bitcoin_transaction_sign_evaluator::do_apply(const bitcoin_transaction_sign_operation &op)
{
try
{
const auto &proposal = op.proposal_id(db());
const auto &sidx = db().get_index_type<son_index>().indices().get<graphene::chain::by_account>();
auto son_obj = sidx.find(op.payer);
db().modify(proposal, [&](proposal_object &po) {
auto bitcoin_transaction_send_op = po.proposed_transaction.operations[0].get<bitcoin_transaction_send_operation>();
bitcoin_transaction_send_op.signatures[son_obj->id] = op.signatures;
po.proposed_transaction.operations[0] = bitcoin_transaction_send_op;
});
update_proposal(op);
}
FC_CAPTURE_AND_RETHROW((op))
}
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;
}
void_result bitcoin_send_transaction_process_evaluator::do_evaluate(const bitcoin_send_transaction_process_operation &op)
{
try
{
FC_ASSERT(db().head_block_time() >= HARDFORK_SON_TIME, "Not allowed until SON HARDFORK"); // can be removed after HF date pass
FC_ASSERT(op.payer == db().get_global_properties().parameters.get_son_btc_account_id(), "Payer should be the son btc account");
const auto& btidx = db().get_index_type<bitcoin_transaction_index>().indices().get<by_id>();
const auto btobj = btidx.find(op.bitcoin_transaction_id);
FC_ASSERT(btobj != btidx.end(), "Bitcoin Transaction Object not found");
FC_ASSERT(btobj->processed == false, "Bitcoin Transaction already processed");
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
object_id_type bitcoin_send_transaction_process_evaluator::do_apply(const bitcoin_send_transaction_process_operation &op)
{
try
{
const auto &btidx = db().get_index_type<bitcoin_transaction_index>().indices().get<by_id>();
auto btobj = btidx.find(op.bitcoin_transaction_id);
if (btobj != btidx.end())
{
db().modify(*btobj, [&op](bitcoin_transaction_object &bto) {
bto.processed = true;
});
}
return op.bitcoin_transaction_id;
}
FC_CAPTURE_AND_RETHROW((op))
}
} // namespace chain
} // namespace graphene

View file

@ -5,6 +5,7 @@ add_library( peerplays_sidechain
sidechain_net_manager.cpp
sidechain_net_handler.cpp
sidechain_net_handler_bitcoin.cpp
bitcoin_utils.cpp
)
if (SUPPORT_MULTIPLE_SONS)

View file

@ -0,0 +1,713 @@
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
#include <fc/io/raw.hpp>
#include <fc/crypto/base58.hpp>
#include <fc/crypto/elliptic.hpp>
#include <fc/crypto/ripemd160.hpp>
#include <fc/crypto/sha256.hpp>
#include <secp256k1.h>
namespace graphene { namespace peerplays_sidechain {
static const unsigned char OP_0 = 0x00;
static const unsigned char OP_1 = 0x51;
static const unsigned char OP_2 = 0x52;
static const unsigned char OP_3 = 0x53;
static const unsigned char OP_4 = 0x54;
static const unsigned char OP_5 = 0x55;
static const unsigned char OP_6 = 0x56;
static const unsigned char OP_7 = 0x57;
static const unsigned char OP_8 = 0x58;
static const unsigned char OP_9 = 0x59;
static const unsigned char OP_10 = 0x5a;
static const unsigned char OP_11 = 0x5b;
static const unsigned char OP_12 = 0x5c;
static const unsigned char OP_13 = 0x5d;
static const unsigned char OP_14 = 0x5e;
static const unsigned char OP_15 = 0x5f;
static const unsigned char OP_16 = 0x60;
static const unsigned char OP_IF = 0x63;
static const unsigned char OP_ENDIF = 0x68;
static const unsigned char OP_SWAP = 0x7c;
static const unsigned char OP_EQUAL = 0x87;
static const unsigned char OP_ADD = 0x93;
static const unsigned char OP_GREATERTHAN = 0xa0;
static const unsigned char OP_HASH160 = 0xa9;
static const unsigned char OP_CHECKSIG = 0xac;
class WriteBytesStream{
public:
WriteBytesStream(bytes& buffer) : storage_(buffer) {}
void write(const unsigned char* d, size_t s) {
storage_.insert(storage_.end(), d, d + s);
}
bool put(unsigned char c) {
storage_.push_back(c);
return true;
}
void writedata8(uint8_t obj)
{
write((unsigned char*)&obj, 1);
}
void writedata16(uint16_t obj)
{
obj = htole16(obj);
write((unsigned char*)&obj, 2);
}
void writedata32(uint32_t obj)
{
obj = htole32(obj);
write((unsigned char*)&obj, 4);
}
void writedata64(uint64_t obj)
{
obj = htole64(obj);
write((unsigned char*)&obj, 8);
}
void write_compact_int(uint64_t val)
{
if (val < 253)
{
writedata8(val);
}
else if (val <= std::numeric_limits<unsigned short>::max())
{
writedata8(253);
writedata16(val);
}
else if (val <= std::numeric_limits<unsigned int>::max())
{
writedata8(254);
writedata32(val);
}
else
{
writedata8(255);
writedata64(val);
}
}
void writedata(const bytes& data)
{
write_compact_int(data.size());
write(&data[0], data.size());
}
private:
bytes& storage_;
};
class ReadBytesStream{
public:
ReadBytesStream(const bytes& buffer, size_t pos = 0) : storage_(buffer), pos_(pos), end_(buffer.size()) {}
size_t current_pos() const { return pos_; }
void set_pos(size_t pos)
{
if(pos > end_)
FC_THROW("Invalid position in BTC tx buffer");
pos_ = pos;
}
inline bool read( unsigned char* d, size_t s ) {
if( end_ - pos_ >= s ) {
memcpy( d, &storage_[pos_], s );
pos_ += s;
return true;
}
FC_THROW( "invalid bitcoin tx buffer" );
}
inline bool get( unsigned char& c )
{
if( pos_ < end_ ) {
c = storage_[pos_++];
return true;
}
FC_THROW( "invalid bitcoin tx buffer" );
}
uint8_t readdata8()
{
uint8_t obj;
read((unsigned char*)&obj, 1);
return obj;
}
uint16_t readdata16()
{
uint16_t obj;
read((unsigned char*)&obj, 2);
return le16toh(obj);
}
uint32_t readdata32()
{
uint32_t obj;
read((unsigned char*)&obj, 4);
return le32toh(obj);
}
uint64_t readdata64()
{
uint64_t obj;
read((unsigned char*)&obj, 8);
return le64toh(obj);
}
uint64_t read_compact_int()
{
uint8_t size = readdata8();
uint64_t ret = 0;
if (size < 253)
{
ret = size;
}
else if (size == 253)
{
ret = readdata16();
if (ret < 253)
FC_THROW("non-canonical ReadCompactSize()");
}
else if (size == 254)
{
ret = readdata32();
if (ret < 0x10000u)
FC_THROW("non-canonical ReadCompactSize()");
}
else
{
ret = readdata64();
if (ret < 0x100000000ULL)
FC_THROW("non-canonical ReadCompactSize()");
}
if (ret > (uint64_t)0x02000000)
FC_THROW("ReadCompactSize(): size too large");
return ret;
}
void readdata(bytes& data)
{
size_t s = read_compact_int();
data.clear();
data.resize(s);
read(&data[0], s);
}
private:
const bytes& storage_;
size_t pos_;
size_t end_;
};
void btc_outpoint::to_bytes(bytes& stream) const
{
WriteBytesStream str(stream);
// TODO: write size?
str.write((unsigned char*)hash.data(), hash.data_size());
str.writedata32(n);
}
size_t btc_outpoint::fill_from_bytes(const bytes& data, size_t pos)
{
ReadBytesStream str(data, pos);
// TODO: read size?
str.read((unsigned char*)hash.data(), hash.data_size());
n = str.readdata32();
return str.current_pos();
}
void btc_in::to_bytes(bytes& stream) const
{
prevout.to_bytes(stream);
WriteBytesStream str(stream);
str.writedata(scriptSig);
str.writedata32(nSequence);
}
size_t btc_in::fill_from_bytes(const bytes& data, size_t pos)
{
pos = prevout.fill_from_bytes(data, pos);
ReadBytesStream str(data, pos);
str.readdata(scriptSig);
nSequence = str.readdata32();
return str.current_pos();
}
void btc_out::to_bytes(bytes& stream) const
{
WriteBytesStream str(stream);
str.writedata64(nValue);
str.writedata(scriptPubKey);
}
size_t btc_out::fill_from_bytes(const bytes& data, size_t pos)
{
ReadBytesStream str(data, pos);
nValue = str.readdata64();
str.readdata(scriptPubKey);
return str.current_pos();
}
void btc_tx::to_bytes(bytes& stream) const
{
WriteBytesStream str(stream);
str.writedata32(nVersion);
if(hasWitness)
{
// write dummy inputs and flag
str.write_compact_int(0);
unsigned char flags = 1;
str.put(flags);
}
str.write_compact_int(vin.size());
for(const auto& in: vin)
in.to_bytes(stream);
str.write_compact_int(vout.size());
for(const auto& out: vout)
out.to_bytes(stream);
if(hasWitness)
{
for(const auto& in: vin)
{
str.write_compact_int(in.scriptWitness.size());
for(const auto& stack_item: in.scriptWitness)
str.writedata(stack_item);
}
}
str.writedata32(nLockTime);
}
size_t btc_tx::fill_from_bytes(const bytes& data, size_t pos)
{
ReadBytesStream ds( data, pos );
nVersion = ds.readdata32();
unsigned char flags = 0;
vin.clear();
vout.clear();
hasWitness = false;
/* Try to read the vin. In case the dummy is there, this will be read as an empty vector. */
size_t vin_size = ds.read_compact_int();
vin.resize(vin_size);
pos = ds.current_pos();
for(auto& in: vin)
pos = in.fill_from_bytes(data, pos);
ds.set_pos(pos);
if (vin_size == 0) {
/* We read a dummy or an empty vin. */
ds.get(flags);
if (flags != 0) {
size_t vin_size = ds.read_compact_int();
vin.resize(vin_size);
pos = ds.current_pos();
for(auto& in: vin)
pos = in.fill_from_bytes(data, pos);
ds.set_pos(pos);
size_t vout_size = ds.read_compact_int();
vout.resize(vout_size);
pos = ds.current_pos();
for(auto& out: vout)
pos = out.fill_from_bytes(data, pos);
ds.set_pos(pos);
hasWitness = true;
}
} else {
/* We read a non-empty vin. Assume a normal vout follows. */
size_t vout_size = ds.read_compact_int();
vout.resize(vout_size);
pos = ds.current_pos();
for(auto& out: vout)
pos = out.fill_from_bytes(data, pos);
ds.set_pos(pos);
}
if (hasWitness) {
/* The witness flag is present, and we support witnesses. */
for (auto& in: vin)
{
unsigned int size = ds.read_compact_int();
in.scriptWitness.resize(size);
for(auto& stack_item: in.scriptWitness)
ds.readdata(stack_item);
}
}
nLockTime = ds.readdata32();
return ds.current_pos();
}
void add_data_to_script(bytes& script, const bytes& data)
{
WriteBytesStream str(script);
str.writedata(data);
}
void add_number_to_script(bytes& script, unsigned char data)
{
WriteBytesStream str(script);
if(data == 0)
str.put(OP_0);
else if(data == 1)
str.put(OP_1);
else if(data == 2)
str.put(OP_2);
else if(data == 3)
str.put(OP_3);
else if(data == 4)
str.put(OP_4);
else if(data == 5)
str.put(OP_5);
else if(data == 6)
str.put(OP_6);
else if(data == 7)
str.put(OP_7);
else if(data == 8)
str.put(OP_8);
else if(data == 9)
str.put(OP_9);
else if(data == 10)
str.put(OP_10);
else if(data == 11)
str.put(OP_11);
else if(data == 12)
str.put(OP_12);
else if(data == 13)
str.put(OP_13);
else if(data == 14)
str.put(OP_14);
else if(data == 15)
str.put(OP_15);
else if(data == 16)
str.put(OP_16);
else
add_data_to_script(script, {data});
}
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int> > key_data)
{
int total_weight = 0;
bytes result;
add_number_to_script(result, 0);
for(auto& p: key_data)
{
total_weight += p.second;
result.push_back(OP_SWAP);
auto raw_data = p.first.serialize();
add_data_to_script(result, bytes(raw_data.begin(), raw_data.begin() + raw_data.size()));
result.push_back(OP_CHECKSIG);
result.push_back(OP_IF);
add_number_to_script(result, static_cast<unsigned char>(p.second));
result.push_back(OP_ADD);
result.push_back(OP_ENDIF);
}
int threshold_weight = 2 * total_weight / 3;
add_number_to_script(result, static_cast<unsigned char>(threshold_weight));
result.push_back(OP_GREATERTHAN);
return result;
}
/** The Bech32 character set for encoding. */
const char* charset = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
/** Concatenate two byte arrays. */
bytes cat(bytes x, const bytes& y) {
x.insert(x.end(), y.begin(), y.end());
return x;
}
/** Expand a HRP for use in checksum computation. */
bytes expand_hrp(const std::string& hrp) {
bytes ret;
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;
}
/** Find the polynomial with value coefficients mod the generator as 30-bit. */
uint32_t polymod(const bytes& values) {
uint32_t chk = 1;
for (size_t i = 0; i < values.size(); ++i) {
uint8_t top = chk >> 25;
chk = (chk & 0x1ffffff) << 5 ^ values[i] ^
(-((top >> 0) & 1) & 0x3b6a57b2UL) ^
(-((top >> 1) & 1) & 0x26508e6dUL) ^
(-((top >> 2) & 1) & 0x1ea119faUL) ^
(-((top >> 3) & 1) & 0x3d4233ddUL) ^
(-((top >> 4) & 1) & 0x2a1462b3UL);
}
return chk;
}
/** Create a checksum. */
bytes bech32_checksum(const std::string& hrp, const bytes& values) {
bytes enc = cat(expand_hrp(hrp), values);
enc.resize(enc.size() + 6);
uint32_t mod = polymod(enc) ^ 1;
bytes ret;
ret.resize(6);
for (size_t i = 0; i < 6; ++i) {
ret[i] = (mod >> (5 * (5 - i))) & 31;
}
return ret;
}
/** Encode a Bech32 string. */
std::string bech32(const std::string& hrp, const bytes& values) {
bytes checksum = bech32_checksum(hrp, values);
bytes combined = cat(values, checksum);
std::string ret = hrp + '1';
ret.reserve(ret.size() + combined.size());
for (size_t i = 0; i < combined.size(); ++i) {
ret += charset[combined[i]];
}
return ret;
}
/** Convert from one power-of-2 number base to another. */
template<int frombits, int tobits, bool pad>
bool convertbits(bytes& out, const bytes& 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;
}
/** Encode a SegWit address. */
std::string segwit_addr_encode(const std::string& hrp, uint8_t witver, const bytes& witprog) {
bytes enc;
enc.push_back(witver);
convertbits<8, 5, true>(enc, witprog);
std::string ret = bech32(hrp, enc);
return ret;
}
std::string p2wsh_address_from_redeem_script(const bytes& script, bitcoin_network network)
{
// calc script hash
fc::sha256 sh = fc::sha256::hash(reinterpret_cast<const char*>(&script[0]), script.size());
bytes wp(sh.data(), sh.data() + sh.data_size());
switch (network) {
case(mainnet):
return segwit_addr_encode("bc", 0, wp);
case(testnet):
case(regtest):
return segwit_addr_encode("tb", 0, wp);
default:
FC_THROW("Unknown bitcoin network type");
}
FC_THROW("Unknown bitcoin network type");
}
bytes lock_script_for_redeem_script(const bytes &script)
{
bytes result;
result.push_back(OP_0);
fc::sha256 h = fc::sha256::hash(reinterpret_cast<const char*>(&script[0]), script.size());
bytes shash(h.data(), h.data() + h.data_size());
add_data_to_script(result, shash);
return result;
}
bytes hash_prevouts(const btc_tx& unsigned_tx)
{
fc::sha256::encoder hasher;
for(const auto& in: unsigned_tx.vin)
{
bytes data;
in.prevout.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
bytes hash_sequence(const btc_tx& unsigned_tx)
{
fc::sha256::encoder hasher;
for(const auto& in: unsigned_tx.vin)
{
hasher.write(reinterpret_cast<const char*>(&in.nSequence), sizeof(in.nSequence));
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
bytes hash_outputs(const btc_tx& unsigned_tx)
{
fc::sha256::encoder hasher;
for(const auto& out: unsigned_tx.vout)
{
bytes data;
out.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
}
fc::sha256 res = fc::sha256::hash(hasher.result());
return bytes(res.data(), res.data() + res.data_size());
}
const secp256k1_context_t* btc_get_context() {
static secp256k1_context_t* ctx = secp256k1_context_create(SECP256K1_CONTEXT_VERIFY | SECP256K1_CONTEXT_SIGN );
return ctx;
}
bytes der_sign(const fc::ecc::private_key& priv_key, const fc::sha256& digest)
{
fc::ecc::signature result;
int size = result.size();
FC_ASSERT( secp256k1_ecdsa_sign( btc_get_context(),
(unsigned char*) digest.data(),
(unsigned char*) result.begin(),
&size,
(unsigned char*) priv_key.get_secret().data(),
secp256k1_nonce_function_rfc6979,
nullptr));
return bytes(result.begin(), result.begin() + size);
}
std::vector<bytes> signature_for_raw_transaction(const bytes& unsigned_tx,
std::vector<uint64_t> in_amounts,
const bytes& redeem_script,
const fc::ecc::private_key& priv_key)
{
btc_tx tx;
tx.fill_from_bytes(unsigned_tx);
FC_ASSERT(tx.vin.size() == in_amounts.size(), "Incorrect input amounts data");
std::vector<bytes> results;
auto cur_amount = in_amounts.begin();
// pre-calc reused values
bytes hashPrevouts = hash_prevouts(tx);
bytes hashSequence = hash_sequence(tx);
bytes hashOutputs = hash_outputs(tx);
// calc digest for every input according to BIP143
// implement SIGHASH_ALL scheme
for(const auto& in: tx.vin)
{
fc::sha256::encoder hasher;
hasher.write(reinterpret_cast<const char*>(&tx.nVersion), sizeof(tx.nVersion));
hasher.write(reinterpret_cast<const char*>(&hashPrevouts[0]), hashPrevouts.size());
hasher.write(reinterpret_cast<const char*>(&hashSequence[0]), hashSequence.size());
bytes data;
in.prevout.to_bytes(data);
hasher.write(reinterpret_cast<const char*>(&data[0]), data.size());
bytes serializedScript;
WriteBytesStream stream(serializedScript);
stream.writedata(redeem_script);
hasher.write(reinterpret_cast<const char*>(&serializedScript[0]), serializedScript.size());
uint64_t amount = *cur_amount++;
hasher.write(reinterpret_cast<const char*>(&amount), sizeof(amount));
hasher.write(reinterpret_cast<const char*>(&in.nSequence), sizeof(in.nSequence));
hasher.write(reinterpret_cast<const char*>(&hashOutputs[0]), hashOutputs.size());
hasher.write(reinterpret_cast<const char*>(&tx.nLockTime), sizeof(tx.nLockTime));
// add sigtype SIGHASH_ALL
uint32_t sigtype = 1;
hasher.write(reinterpret_cast<const char*>(&sigtype), sizeof(sigtype));
fc::sha256 digest = fc::sha256::hash(hasher.result());
//std::vector<char> res = priv_key.sign(digest);
//bytes s_data(res.begin(), res.end());
bytes s_data = der_sign(priv_key, digest);
s_data.push_back(1);
results.push_back(s_data);
}
return results;
}
bytes sign_pw_transfer_transaction(const bytes &unsigned_tx, std::vector<uint64_t> in_amounts, const bytes& redeem_script, const std::vector<fc::optional<fc::ecc::private_key> > &priv_keys)
{
btc_tx tx;
tx.fill_from_bytes(unsigned_tx);
bytes dummy_data;
for(auto key: priv_keys)
{
if(key)
{
std::vector<bytes> signatures = signature_for_raw_transaction(unsigned_tx, in_amounts, redeem_script, *key);
FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number");
// push signatures in reverse order because script starts to check the top signature on the stack first
for(unsigned int i = 0; i < tx.vin.size(); i++)
tx.vin[i].scriptWitness.insert(tx.vin[i].scriptWitness.begin(), signatures[i]);
}
else
{
for(unsigned int i = 0; i < tx.vin.size(); i++)
tx.vin[i].scriptWitness.push_back(dummy_data);
}
}
for(auto& in: tx.vin)
{
in.scriptWitness.push_back(redeem_script);
}
tx.hasWitness = true;
bytes ret;
tx.to_bytes(ret);
return ret;
}
bytes add_dummy_signatures_for_pw_transfer(const bytes& unsigned_tx,
const bytes& redeem_script,
unsigned int key_count)
{
btc_tx tx;
tx.fill_from_bytes(unsigned_tx);
bytes dummy_data;
for(auto& in: tx.vin)
{
for(unsigned i = 0; i < key_count; i++)
in.scriptWitness.push_back(dummy_data);
in.scriptWitness.push_back(redeem_script);
}
tx.hasWitness = true;
bytes ret;
tx.to_bytes(ret);
return ret;
}
bytes partially_sign_pw_transfer_transaction(const bytes& partially_signed_tx,
std::vector<uint64_t> in_amounts,
const fc::ecc::private_key& priv_key,
unsigned int key_idx)
{
btc_tx tx;
tx.fill_from_bytes(partially_signed_tx);
FC_ASSERT(tx.vin.size() > 0);
bytes redeem_script = tx.vin[0].scriptWitness.back();
std::vector<bytes> signatures = signature_for_raw_transaction(partially_signed_tx, in_amounts, redeem_script, priv_key);
FC_ASSERT(signatures.size() == tx.vin.size(), "Invalid signatures number");
// push signatures in reverse order because script starts to check the top signature on the stack first
unsigned witness_idx = tx.vin[0].scriptWitness.size() - 2 - key_idx;
for(unsigned int i = 0; i < tx.vin.size(); i++)
tx.vin[i].scriptWitness[witness_idx] = signatures[i];
bytes ret;
tx.to_bytes(ret);
return ret;
}
}}

View file

@ -0,0 +1,78 @@
#pragma once
#include <graphene/peerplays_sidechain/defs.hpp>
#include <fc/optional.hpp>
namespace graphene { namespace peerplays_sidechain {
enum bitcoin_network {
mainnet,
testnet,
regtest
};
bytes generate_redeem_script(std::vector<std::pair<fc::ecc::public_key, int> > key_data);
std::string p2wsh_address_from_redeem_script(const bytes& script, bitcoin_network network = mainnet);
bytes lock_script_for_redeem_script(const bytes& script);
/*
* unsigned_tx - tx, all inputs of which are spends of the PW P2SH address
* returns signed transaction
*/
bytes sign_pw_transfer_transaction(const bytes& unsigned_tx,
std::vector<uint64_t> in_amounts,
const bytes& redeem_script,
const std::vector<fc::optional<fc::ecc::private_key>>& priv_keys);
bytes add_dummy_signatures_for_pw_transfer(const bytes& unsigned_tx,
const bytes& redeem_script,
unsigned int key_count);
bytes partially_sign_pw_transfer_transaction(const bytes& partially_signed_tx,
std::vector<uint64_t> in_amounts,
const fc::ecc::private_key& priv_key,
unsigned int key_idx);
struct btc_outpoint
{
fc::uint256 hash;
uint32_t n;
void to_bytes(bytes& stream) const;
size_t fill_from_bytes(const bytes& data, size_t pos = 0);
};
struct btc_in
{
btc_outpoint prevout;
bytes scriptSig;
uint32_t nSequence;
std::vector<bytes> scriptWitness;
void to_bytes(bytes& stream) const;
size_t fill_from_bytes(const bytes& data, size_t pos = 0);
};
struct btc_out
{
int64_t nValue;
bytes scriptPubKey;
void to_bytes(bytes& stream) const;
size_t fill_from_bytes(const bytes& data, size_t pos = 0);
};
struct btc_tx
{
std::vector<btc_in> vin;
std::vector<btc_out> vout;
int32_t nVersion;
uint32_t nLockTime;
bool hasWitness;
void to_bytes(bytes& stream) const;
size_t fill_from_bytes(const bytes& data, size_t pos = 0);
};
}}

View file

@ -18,7 +18,7 @@ enum class sidechain_type {
peerplays
};
using bytes = std::vector<char>;
using bytes = std::vector<unsigned char>;
struct prev_out
{

View file

@ -11,6 +11,14 @@
namespace graphene { namespace peerplays_sidechain {
class btc_txout
{
public:
std::string txid_;
unsigned int out_num_;
double amount_;
};
class bitcoin_rpc_client {
public:
bitcoin_rpc_client( std::string _ip, uint32_t _rpc, std::string _user, std::string _password) ;
@ -24,6 +32,9 @@ public:
std::string create_raw_transaction(const sidechain_event_data& sed, const std::string& pw_address);
std::string sign_raw_transaction_with_wallet(const std::string& tx_hash);
std::string sign_raw_transaction_with_privkey(const std::string& tx_hash, const std::string& private_key);
void import_address( const std::string& address_or_script);
std::vector<btc_txout> list_unspent();
std::string prepare_tx(const std::vector<btc_txout>& ins, const fc::flat_map<std::string, double> outs);
private:
@ -79,6 +90,7 @@ private:
uint32_t rpc_port;
std::string rpc_user;
std::string rpc_password;
std::map<std::string, std::string> _private_keys;
std::unique_ptr<zmq_listener> listener;
std::unique_ptr<bitcoin_rpc_client> bitcoin_client;

View file

@ -248,6 +248,120 @@ bool bitcoin_rpc_client::connection_is_not_defined() const
return ip.empty() || rpc_port == 0 || user.empty() || password.empty();
}
void bitcoin_rpc_client::import_address(const std::string &address_or_script)
{
const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": \"importaddress\", \"params\": [") +
std::string("\"") + address_or_script + std::string("\"") + std::string("] }");
const auto reply = send_post_request( body );
if( reply.body.empty() )
{
wlog("Failed to import address [${addr}]", ("addr", address_or_script));
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((address_or_script)(reply_str));
return;
} else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) {
wlog( "Failed to import address [${addr}]! Reply: ${msg}", ("addr", address_or_script)("msg", reply_str) );
}
}
std::vector<btc_txout> bitcoin_rpc_client::list_unspent()
{
const auto body = std::string("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": \"listunspent\", \"params\": [] }");
const auto reply = send_post_request( body );
std::vector<btc_txout> result;
if( reply.body.empty() )
{
wlog("Failed to list unspent txo");
return result;
}
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((reply_str));
if( json.count( "result" ) )
{
for(auto& entry: json.get_child("result"))
{
btc_txout txo;
txo.txid_ = entry.second.get_child("txid").get_value<std::string>();
txo.out_num_ = entry.second.get_child("vout").get_value<unsigned int>();
txo.amount_ = entry.second.get_child("amount").get_value<double>();
result.push_back(txo);
}
}
} else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) {
wlog( "Failed to list unspent txo! Reply: ${msg}", ("msg", reply_str) );
}
return result;
}
std::string bitcoin_rpc_client::prepare_tx(const std::vector<btc_txout> &ins, const fc::flat_map<std::string, double> outs)
{
std::string body("{\"jsonrpc\": \"1.0\", \"id\":\"pp_plugin\", \"method\": \"createrawtransaction\", \"params\": [");
body += "[";
bool first = true;
for(const auto& entry: ins)
{
if(!first)
body += ",";
body += "{\"txid\":\"" + entry.txid_ + "\",\"vout\":\"" + fc::to_string(entry.out_num_) + "\"}";
first = false;
}
body += "]";
first = true;
body += "{";
for(const auto& entry: outs)
{
if(!first)
body += ",";
body += "\"" + entry.first + "\":\"" + fc::to_string(entry.second) + "\"";
first = false;
}
body += "}";
body += std::string("] }");
const auto reply = send_post_request( body );
if( reply.body.empty() )
{
wlog("Failed to create raw transaction: [${body}]", ("body", body));
return std::string();
}
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((reply_str));
if( json.count( "result" ) )
return json.get_child("result").get_value<std::string>();
} else if( json.count( "error" ) && !json.get_child( "error" ).empty() ) {
wlog( "Failed to create raw transaction: [${body}]! Reply: ${msg}", ("body", body)("msg", reply_str) );
}
return std::string();
}
fc::http::reply bitcoin_rpc_client::send_post_request( std::string body )
{
fc::http::connection conn;
@ -310,6 +424,21 @@ sidechain_net_handler_bitcoin::sidechain_net_handler_bitcoin(peerplays_sidechain
rpc_user = options.at("bitcoin-node-rpc-user").as<std::string>();
rpc_password = options.at("bitcoin-node-rpc-password").as<std::string>();
if( options.count("bitcoin-private-keys") )
{
const std::vector<std::string> pub_priv_keys = options["bitcoin-private-keys"].as<std::vector<std::string>>();
for (const std::string& itr_key_pair : pub_priv_keys)
{
auto key_pair = graphene::app::dejsonify<std::pair<std::string, std::string> >(itr_key_pair, 5);
ilog("Public Key: ${public}", ("public", key_pair.first));
if(!key_pair.first.length() || !key_pair.second.length())
{
FC_THROW("Invalid public private key pair.");
}
_private_keys[key_pair.first] = key_pair.second;
}
}
fc::http::connection conn;
try {
conn.connect_to( fc::ip::endpoint( fc::ip::address( ip ), rpc_port ) );

View file

@ -47,6 +47,7 @@
#include <graphene/chain/son_wallet_object.hpp>
#include <graphene/chain/son_wallet_transfer_object.hpp>
#include <graphene/chain/sidechain_address_object.hpp>
#include <graphene/chain/sidechain_transaction_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <iostream>

View file

@ -0,0 +1,316 @@
#include <boost/test/unit_test.hpp>
#include <graphene/peerplays_sidechain/bitcoin_utils.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/crypto/elliptic.hpp>
#include <fc/crypto/base58.hpp>
#include <secp256k1.h>
using namespace graphene::peerplays_sidechain;
BOOST_AUTO_TEST_CASE(tx_serialization)
{
// use real mainnet transaction
// txid: 6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315
// json: {"txid":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","hash":"6189e3febb5a21cee8b725aa1ef04ffce7e609448446d3a8d6f483c634ef5315","version":1,"size":224,"vsize":224,"weight":896,"locktime":0,"vin":[{"txid":"55d079ca797fee81416b71b373abedd8722e33c9f73177be0166b5d5fdac478b","vout":0,"scriptSig":{"asm":"3045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253[ALL] 02be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4","hex":"483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4"},"sequence":4294967295}],"vout":[{"value":1.26491535,"n":0,"scriptPubKey":{"asm":"OP_DUP OP_HASH160 95783804d28e528fbc4b48c7700471e6845804eb OP_EQUALVERIFY OP_CHECKSIG","hex":"76a91495783804d28e528fbc4b48c7700471e6845804eb88ac","reqSigs":1,"type":"pubkeyhash","addresses":["1EdKhXv7zjGowPzgDQ4z1wa2ukVrXRXXkP"]}},{"value":0.0002,"n":1,"scriptPubKey":{"asm":"OP_HASH160 fb0670971091da8248b5c900c6515727a20e8662 OP_EQUAL","hex":"a914fb0670971091da8248b5c900c6515727a20e866287","reqSigs":1,"type":"scripthash","addresses":["3QaKF8zobqcqY8aS6nxCD5ZYdiRfL3RCmU"]}}]}
// hex: "01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000"
fc::string strtx("01000000018b47acfdd5b56601be7731f7c9332e72d8edab73b3716b4181ee7f79ca79d055000000006b483045022100d82e57d4d11d3b811d07f2fa4ded2fb8a3b7bb1d3e9f293433de5c0d1093c3bd02206704ccd2ff437e2f7716b5e9f2502a9cbb41f1245a18b2b10296980f1ae38253012102be9919a5ba373b1af58ad757db19e7c836116bb8138e0c6d99599e4db96568f4ffffffff028f1b8a07000000001976a91495783804d28e528fbc4b48c7700471e6845804eb88ac204e00000000000017a914fb0670971091da8248b5c900c6515727a20e86628700000000");
bytes bintx;
bintx.resize(strtx.length() / 2);
fc::from_hex(strtx, reinterpret_cast<char*>(&bintx[0]), bintx.size());
btc_tx tx;
BOOST_CHECK_NO_THROW(tx.fill_from_bytes(bintx));
BOOST_CHECK(tx.nVersion == 1);
BOOST_CHECK(tx.nLockTime == 0);
BOOST_CHECK(tx.vin.size() == 1);
BOOST_CHECK(tx.vout.size() == 2);
bytes buff;
tx.to_bytes(buff);
BOOST_CHECK(bintx == buff);
}
BOOST_AUTO_TEST_CASE(pw_transfer)
{
// key set for the old Primary Wallet
std::vector<fc::ecc::private_key> priv_old;
for(unsigned i = 0; i < 15; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_old.push_back(fc::ecc::private_key::generate_from_seed(h));
}
// print old keys
for(auto key: priv_old)
{
fc::sha256 secret = key.get_secret();
bytes data({239});
data.insert(data.end(), secret.data(), secret.data() + secret.data_size());
fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size()));
data.insert(data.end(), cs.data(), cs.data() + 4);
}
std::vector<fc::ecc::public_key> pub_old;
for(auto& key: priv_old)
pub_old.push_back(key.get_public_key());
// old key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
for(unsigned i = 0; i < 15; ++i)
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
// redeem script for old PW
bytes redeem_old =generate_redeem_script(weights_old);
// Old PW address
std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet);
// This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766
BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e");
bytes scriptPubKey = lock_script_for_redeem_script(redeem_old);
// key set for the new Primary Wallet
std::vector<fc::ecc::private_key> priv_new;
for(unsigned i = 16; i < 31; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_new.push_back(fc::ecc::private_key::generate_from_seed(h));
}
std::vector<fc::ecc::public_key> pub_new;
for(auto& key: priv_new)
pub_new.push_back(key.get_public_key());
// new key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
for(unsigned i = 0; i < 15; ++i)
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
// redeem script for new PW
bytes redeem_new =generate_redeem_script(weights_new);
// New PW address
std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet);
BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y");
// try to move funds from old wallet to new one
// get unspent outputs for old wallet with list_uspent (address should be
// added to wallet with import_address before). It should return
// 1 UTXO: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766:0]
// with 20000 satoshis
// So, we creating a raw transaction with 1 input and one output that gets
// 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx)
// Here we just serialize the transaction without scriptSig in inputs then sign it.
btc_outpoint outpoint;
outpoint.hash = fc::uint256("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766");
// reverse hash due to the different from_hex algo
std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size());
outpoint.n = 0;
btc_in input;
input.prevout = outpoint;
input.nSequence = 0xffffffff;
btc_out output;
output.nValue = 19000;
output.scriptPubKey = lock_script_for_redeem_script(redeem_new);
btc_tx tx;
tx.nVersion = 2;
tx.nLockTime = 0;
tx.hasWitness = false;
tx.vin.push_back(input);
tx.vout.push_back(output);
bytes unsigned_tx;
tx.to_bytes(unsigned_tx);
std::vector<uint64_t> in_amounts({20000});
std::vector<fc::optional<fc::ecc::private_key>> keys_to_sign;
for(auto key: priv_old)
keys_to_sign.push_back(fc::optional<fc::ecc::private_key>(key));
bytes signed_tx =sign_pw_transfer_transaction(unsigned_tx, in_amounts, redeem_old, keys_to_sign);
// this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850
BOOST_CHECK(fc::to_hex((char*)&signed_tx[0], signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000");
}
BOOST_AUTO_TEST_CASE(pw_separate_sign)
{
// key set for the old Primary Wallet
std::vector<fc::ecc::private_key> priv_old;
for(unsigned i = 0; i < 15; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_old.push_back(fc::ecc::private_key::generate_from_seed(h));
}
// print old keys
for(auto key: priv_old)
{
fc::sha256 secret = key.get_secret();
bytes data({239});
data.insert(data.end(), secret.data(), secret.data() + secret.data_size());
fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size()));
data.insert(data.end(), cs.data(), cs.data() + 4);
}
std::vector<fc::ecc::public_key> pub_old;
for(auto& key: priv_old)
pub_old.push_back(key.get_public_key());
// old key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
for(unsigned i = 0; i < 15; ++i)
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
// redeem script for old PW
bytes redeem_old =generate_redeem_script(weights_old);
// Old PW address
std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet);
// This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766
BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e");
bytes scriptPubKey = lock_script_for_redeem_script(redeem_old);
// key set for the new Primary Wallet
std::vector<fc::ecc::private_key> priv_new;
for(unsigned i = 16; i < 31; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_new.push_back(fc::ecc::private_key::generate_from_seed(h));
}
std::vector<fc::ecc::public_key> pub_new;
for(auto& key: priv_new)
pub_new.push_back(key.get_public_key());
// new key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
for(unsigned i = 0; i < 15; ++i)
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
// redeem script for new PW
bytes redeem_new =generate_redeem_script(weights_new);
// New PW address
std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet);
BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y");
// try to move funds from old wallet to new one
// get unspent outputs for old wallet with list_uspent (address should be
// added to wallet with import_address before). It should return
// 1 UTXO: [508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766:0]
// with 20000 satoshis
// So, we creating a raw transaction with 1 input and one output that gets
// 20000 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx)
// Here we just serialize the transaction without scriptSig in inputs then sign it.
btc_outpoint outpoint;
outpoint.hash = fc::uint256("508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766");
// reverse hash due to the different from_hex algo
std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size());
outpoint.n = 0;
btc_in input;
input.prevout = outpoint;
input.nSequence = 0xffffffff;
btc_out output;
output.nValue = 19000;
output.scriptPubKey = lock_script_for_redeem_script(redeem_new);
btc_tx tx;
tx.nVersion = 2;
tx.nLockTime = 0;
tx.hasWitness = false;
tx.vin.push_back(input);
tx.vout.push_back(output);
bytes unsigned_tx;
tx.to_bytes(unsigned_tx);
std::vector<uint64_t> in_amounts({20000});
// prepare tx with dummy signs
bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15);
// sign with every old key one by one
for(unsigned idx = 0; idx < 15; idx++)
partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx);
// now this is real testnet tx with id 1734a2f6192c3953c90f9fd7f69eba16eeb0922207f81f3af32d6534a6f8e850
BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "020000000001016617ba8fec01d942ef23dfa26c99badceb682050c5e67ec5b76de65dd6368a500000000000ffffffff01384a0000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10473044022028cf6df7ed5c2761d7aa2af20717c8b5ace168a7800d6a566f2c1ae28160cae502205e01a3d91f5b9870577e36fbc26ce0cecc3e628cc376c7016364ec3f370703140147304402205c9a88cbe41eb9c6a16ba1d747456222cbe951d04739d21309ef0c0cf00727f202202d06db830ee5823882c7b6f82b708111a8f37741878896cd3558fb91efe8076401473044022009c3184fc0385eb7ed8dc0374791cbdace0eff0dc27dd80ac68f8cb81110f700022042267e8a8788c314347234ea10db6c1ec21a2d423b784cbfbaadf3b2393c44630147304402202363ce306570dc0bbf6d18d41b67c6488a014a91d8e24c03670b4f65523aca12022029d04c114b8e93d982cadee89d80bb25c5c8bc437d6cd2bfce8e0d83a08d14410148304502210087b4742e5cf9c77ca9f99928e7c7087e7d786e09216485628509e4e0b2f29d7e02207daf2eaee9fe8bf117074be137b7ae4b8503a4f6d263424e8e6a16405d5b723c0147304402204f1c3ed8cf595bfaf79d90f4c55c04c17bb6d446e3b9beca7ee6ee7895c6b752022022ac032f219a81b2845d0a1abfb904e40036a3ad332e7dfada6fda21ef7080b501483045022100d020eca4ba1aa77de9caf98f3a29f74f55268276860b9fa35fa16cfc00219dd8022028237de6ad063116cf8182d2dd45a09cb90c2ec8104d793eb3635a1290027cd6014730440220322193b0feba7356651465b86463c7619cd3d96729df6242e9571c74ff1c3c2902206e1de8e77b71c7b6031a934b52321134b6a8d138e2124e90f6345decbd543efb01483045022100d70ade49b3f17812785a41711e107b27c3d4981f8e12253629c07ec46ee511af02203e1ea9059ed9165eeff827002c7399a30c478a9b6f2b958621bfbc6713ab4dd30147304402206f7f10d9993c7019360276bbe790ab587adadeab08088593a9a0c56524aca4df02207c147fe2e51484801a4e059e611e7514729d685a5df892dcf02ba59d455e678101483045022100d5071b8039364bfaa53ef5e22206f773539b082f28bd1fbaaea995fa28aae0f5022056edf7a7bdd8a9a54273a667be5bcd11191fc871798fb44f6e1e35c95d86a81201483045022100a39f8ffbcd9c3f0591fc731a9856c8e024041017cba20c9935f13e4abcf9e9dc0220786823b8cd55664ff9ad6277899aacfd56fa8e48c38881482418b7d50ca27211014730440220361d3b87fcc2b1c12a9e7c684c78192ccb7fe51b90c281b7058384b0b036927a0220434c9b403ee3802b4e5b53feb9bb37d2a9d8746c3688da993549dd9d9954c6800147304402206dc4c3a4407fe9cbffb724928aa0597148c14a20d0d7fbb36ad5d3e2a3abf85e022039ef7baebbf08494495a038b009c6d4ff4b91c38db840673b87f6c27c3b53e7e01483045022100cadac495ea78d0ce9678a4334b8c43f7fafeea5a59413cc2a0144addb63485f9022078ca133e020e3afd0e79936337afefc21d84d3839f5a225a0f3d3eebc15f959901fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000");
}
BOOST_AUTO_TEST_CASE(pw_partially_sign)
{
// key set for the old Primary Wallet
std::vector<fc::ecc::private_key> priv_old;
for(unsigned i = 0; i < 15; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_old.push_back(fc::ecc::private_key::generate_from_seed(h));
}
// print old keys
for(auto key: priv_old)
{
fc::sha256 secret = key.get_secret();
bytes data({239});
data.insert(data.end(), secret.data(), secret.data() + secret.data_size());
fc::sha256 cs = fc::sha256::hash(fc::sha256::hash((char*)&data[0], data.size()));
data.insert(data.end(), cs.data(), cs.data() + 4);
}
std::vector<fc::ecc::public_key> pub_old;
for(auto& key: priv_old)
pub_old.push_back(key.get_public_key());
// old key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_old;
for(unsigned i = 0; i < 15; ++i)
weights_old.push_back(std::make_pair(pub_old[i], i + 1));
// redeem script for old PW
bytes redeem_old =generate_redeem_script(weights_old);
// Old PW address
std::string old_pw = p2wsh_address_from_redeem_script(redeem_old, bitcoin_network::testnet);
// This address was filled with testnet transaction 508a36d65de66db7c57ee6c5502068ebdcba996ca2df23ef42d901ec8fba1766
BOOST_REQUIRE(old_pw == "tb1qfhstznulf5cmjzahlkmnuuvs0tkjtwjlme3ugz8jzfjanf8h5rwsp45t7e");
bytes scriptPubKey = lock_script_for_redeem_script(redeem_old);
// key set for the new Primary Wallet
std::vector<fc::ecc::private_key> priv_new;
for(unsigned i = 16; i < 31; ++i)
{
const char* seed = reinterpret_cast<const char*>(&i);
fc::sha256 h = fc::sha256::hash(seed, sizeof(i));
priv_new.push_back(fc::ecc::private_key::generate_from_seed(h));
}
std::vector<fc::ecc::public_key> pub_new;
for(auto& key: priv_new)
pub_new.push_back(key.get_public_key());
// new key weights
std::vector<std::pair<fc::ecc::public_key, int> > weights_new;
for(unsigned i = 0; i < 15; ++i)
weights_new.push_back(std::make_pair(pub_new[i], 16 - i));
// redeem script for new PW
bytes redeem_new =generate_redeem_script(weights_new);
// New PW address
std::string new_pw = p2wsh_address_from_redeem_script(redeem_new, bitcoin_network::testnet);
BOOST_REQUIRE(new_pw == "tb1qzegrz8r8z8ddfkql8595d90czng6eyjmx4ur73ls4pq57jg99qhsh9fd2y");
// try to move funds from old wallet to new one
// Spent 1 UTXO: [7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf:0]
// with 29999 satoshis
// So, we creating a raw transaction with 1 input and one output that gets
// 29999 - fee satoshis with createrawtransaction call (bitcoin_rpc_client::prepare_tx)
btc_outpoint outpoint;
outpoint.hash = fc::uint256("7007b77fcd5fe097d02679252aa112900d08ab20c06052f4148265b21b1f9fbf");
// reverse hash due to the different from_hex algo
std::reverse(outpoint.hash.data(), outpoint.hash.data() + outpoint.hash.data_size());
outpoint.n = 0;
btc_in input;
input.prevout = outpoint;
input.nSequence = 0xffffffff;
btc_out output;
output.nValue = 29000;
output.scriptPubKey = lock_script_for_redeem_script(redeem_new);
btc_tx tx;
tx.nVersion = 2;
tx.nLockTime = 0;
tx.hasWitness = false;
tx.vin.push_back(input);
tx.vout.push_back(output);
bytes unsigned_tx;
tx.to_bytes(unsigned_tx);
std::vector<uint64_t> in_amounts({29999});
// prepare tx with dummy signs
bytes partially_signed_tx = add_dummy_signatures_for_pw_transfer(unsigned_tx, redeem_old, 15);
// sign with every old key one by one except the first one
for(unsigned idx = 1; idx < 15; idx++)
partially_signed_tx = partially_sign_pw_transfer_transaction(partially_signed_tx, in_amounts, priv_old[idx], idx);
// now this is real testnet tx with id e86455c40da6993b6fed70daea2046287b206ab5c16e1ab58c4dfb4a7d6efb84
BOOST_CHECK(fc::to_hex((char*)&partially_signed_tx[0], partially_signed_tx.size()) == "02000000000101bf9f1f1bb2658214f45260c020ab080d9012a12a257926d097e05fcd7fb707700000000000ffffffff0148710000000000002200201650311c6711dad4d81f3d0b4695f814d1ac925b35783f47f0a8414f4905282f10483045022100c4c567419754c5c1768e959a35633012e8d22ccc90d7cd1b88d6d430a513fbbd0220729c2a3520d0cae7dd6dcd928624ffa3e0b6ce0c4f5c340653a6c18549182588014830450221008c868ea2cdf5b23bdf9e6c7d7c283b8424aeb4aec43621424baef1ee77dd399a02205431f608006f0f0dcd392fab4f25328808b45d4a73852a197e947b289faefece01483045022100aecac85bbb81bc0a4e127c15090c5ab82a62b9e27a9a6eb8eddf8de294aa9d920220482f2ba8d7b62e9f3f7a68b0ef3236bc56e44481d3eb59f62d1daf4b191dc86001483045022100eb27943f8b511a36b1a843f9b3ddf6930aece5a3c0be697dbafc921924fc049c022065ba3e1e4ad57f56337143136c5d3ee3f56dd60f36e798f07b5646e29343d7320147304402206e24158484ebb2cd14b9c410ecd04841d806d8464ce9a827533484c8ad8d921b022021baec9cd0ad46e7b19c8de7df286093b835df5c6243e90b14f5748dc1b7c13901473044022067bfaf0e39d72e49a081d4e43828746ab7524c4764e445173dd96cc7e6187d46022063ef107375cc45d1c26b1e1c87b97694f71645187ad871db9c05b8e981a0da8601483045022100da0162de3e4a5268b616b9d01a1a4f64b0c371c6b44fb1f740a264455f2bc20d02203a0b45a98a341722ad65ae4ad68538d617b1cfbb229751f875615317eaf15dd4014830450221008220c4f97585e67966d4435ad8497eb89945f13dd8ff24048b830582349041a002204cb03f7271895637a31ce6479d15672c2d70528148e3cd6196e6f722117745c50147304402203e83ab4b15bb0680f82779335acf9a3ce45316150a4538d5e3d25cb863fcec5702204b3913874077ed2cae4e10f8786053b6f157973a54d156d5863f13accca595f50147304402201420d2a2830278ffff5842ecb7173a23642f179435443e780b3d1fe04be5c32e02203818202390e0e63b4309b89f9cce08c0f4dfa539c2ed59b05e24325671e2747c0147304402205624ca9d47ae04afd8fff705706d6853f8c679abb385f19e01c36f9380a0bad602203dc817fc55497e4c1759a3dbfff1662faca593a9f10d3a9b3e24d5ee3165d4400147304402203a959f9a34587c56b86826e6ab65644ab19cbd09ca078459eb59956b02bc753002206df5ded568d0e3e3645f8cb8ca02874dd1bfa82933eb5e01ff2e5a773633e51601483045022100a84ed5be60b9e095d40f3f6bd698425cb9c4d8f95e8b43ca6c5120a6c599e9eb022064c703952d18d753f9198d78188a26888e6b06c832d93f8075311d57a13240160147304402202e71d3af33a18397b90072098881fdbdb8d6e4ffa34d21141212dd815c97d00f02207195f1c06a8f44ca72af15fdaba89b07cf6daef9be981c432b9f5c10f1e374200100fd5c02007c21030e88484f2bb5dcfc0b326e9eb565c27c8291efb064d060d226916857a2676e62ac635193687c2102151ad794a3aeb3cf9c190120da3d13d36cd8bdf21ca1ccb15debd61c601314b0ac635293687c2103b45a5955ea7847d121225c752edaeb4a5d731a056a951a876caaf6d1f69adb7dac635393687c2102def03a6ffade4ffb0017c8d93859a247badd60e2d76d00e2a3713f6621932ec1ac635493687c21035f17aa7d58b8c3ee0d87240fded52b27f3f12768a0a54ba2595e0a929dd87155ac635593687c2103c8582ac6b0bd20cc1b02c6a86bad2ea10cadb758fedd754ba0d97be85b63b5a7ac635693687c21028148a1f9669fc4471e76f7a371d7cc0563b26e0821d9633fd37649744ff54edaac635793687c2102f0313701b0035f0365a59ce1a3d7ae7045e1f2fb25c4656c08071e5baf51483dac635893687c21024c4c25d08173b3c4d4e1375f8107fd7040c2dc0691ae1bf6fe82b8c88a85185fac635993687c210360fe2daa8661a3d25d0df79875d70b1c3d443ade731caafda7488cb68b4071b0ac635a93687c210250e41a6a4abd7b0b3a49eaec24a6fafa99e5aa7b1e3a5aabe60664276df3d937ac635b93687c2103045a32125930ca103c7d7c79b6f379754796cd4ea7fb0059da926e415e3877d3ac635c93687c210344943249d7ca9b47316fef0c2a413dda3a75416a449a29f310ab7fc9d052ed70ac635d93687c2103c62967320b63df5136ff1ef4c7959ef5917ee5a44f75c83e870bc488143d4d69ac635e93687c21020429f776e15770e4dc52bd6f72e6ed6908d51de1c4a64878433c4e3860a48dc4ac635f93680150a000000000");
}

272
tests/tests/history_api_tests.cpp Normal file → Executable file
View file

@ -407,143 +407,143 @@ BOOST_AUTO_TEST_CASE(get_account_history_additional) {
} FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(track_account) {
try {
graphene::app::history_api hist_api(app);
//BOOST_AUTO_TEST_CASE(track_account) {
// try {
// graphene::app::history_api hist_api(app);
//
// // account_id_type() is not tracked
//
// // account_id_type() creates alice(not tracked account)
// const account_object& alice = create_account("alice");
// auto alice_id = alice.id;
//
// //account_id_type() creates some ops
// create_bitasset("CNY", account_id_type());
// create_bitasset("USD", account_id_type());
//
// // account_id_type() creates dan(account tracked)
// const account_object& dan = create_account("dan");
// auto dan_id = dan.id;
//
// // dan makes 1 op
// create_bitasset("EUR", dan_id);
//
// generate_block( ~database::skip_fork_db );
//
// // anything against account_id_type() should be {}
// vector<operation_history_object> histories =
// hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 1, operation_history_id_type(2));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
//
// // anything against alice should be {}
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(2));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
//
// // dan should have history
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 2u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);
// BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);
//
// // create more ops, starting with an untracked account
// create_bitasset( "BTC", account_id_type() );
// create_bitasset( "GBP", dan_id );
//
// generate_block( ~database::skip_fork_db );
//
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 3u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);
// BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);
// BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);
//
// db.pop_block();
//
// // Try again, should result in same object IDs
// create_bitasset( "BTC", account_id_type() );
// create_bitasset( "GBP", dan_id );
//
// generate_block();
//
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 3u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);
// BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);
// BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);
// } catch (fc::exception &e) {
// edump((e.to_detail_string()));
// throw;
// }
//}
// account_id_type() is not tracked
// account_id_type() creates alice(not tracked account)
const account_object& alice = create_account("alice");
auto alice_id = alice.id;
//account_id_type() creates some ops
create_bitasset("CNY", account_id_type());
create_bitasset("USD", account_id_type());
// account_id_type() creates dan(account tracked)
const account_object& dan = create_account("dan");
auto dan_id = dan.id;
// dan makes 1 op
create_bitasset("EUR", dan_id);
generate_block( ~database::skip_fork_db );
// anything against account_id_type() should be {}
vector<operation_history_object> histories =
hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(1), 1, operation_history_id_type(2));
BOOST_CHECK_EQUAL(histories.size(), 0u);
// anything against alice should be {}
histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(2));
BOOST_CHECK_EQUAL(histories.size(), 0u);
// dan should have history
histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 2u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);
BOOST_CHECK_EQUAL(histories[1].id.instance(), 3u);
// create more ops, starting with an untracked account
create_bitasset( "BTC", account_id_type() );
create_bitasset( "GBP", dan_id );
generate_block( ~database::skip_fork_db );
histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 3u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);
BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);
BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);
db.pop_block();
// Try again, should result in same object IDs
create_bitasset( "BTC", account_id_type() );
create_bitasset( "GBP", dan_id );
generate_block();
histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 3u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 6u);
BOOST_CHECK_EQUAL(histories[1].id.instance(), 4u);
BOOST_CHECK_EQUAL(histories[2].id.instance(), 3u);
} catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
}
}
BOOST_AUTO_TEST_CASE(track_account2) {
try {
graphene::app::history_api hist_api(app);
// account_id_type() is tracked
// account_id_type() creates alice(tracked account)
const account_object& alice = create_account("alice");
auto alice_id = alice.id;
//account_id_type() creates some ops
create_bitasset("CNY", account_id_type());
create_bitasset("USD", account_id_type());
// alice makes 1 op
create_bitasset("EUR", alice_id);
// account_id_type() creates dan(account not tracked)
const account_object& dan = create_account("dan");
auto dan_id = dan.id;
generate_block();
// all account_id_type() should have 4 ops {4,2,1,0}
vector<operation_history_object> histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 4u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);
BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);
BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);
BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);
// all alice account should have 2 ops {3, 0}
histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 2u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);
BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);
// alice first op should be {0}
histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 1, operation_history_id_type(1));
BOOST_CHECK_EQUAL(histories.size(), 1u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);
// alice second op should be {3}
histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 1u);
BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);
// anything against dan should be {}
histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 10, operation_history_id_type(0));
BOOST_CHECK_EQUAL(histories.size(), 0u);
histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 1, operation_history_id_type(2));
BOOST_CHECK_EQUAL(histories.size(), 0u);
} catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
}
}
//BOOST_AUTO_TEST_CASE(track_account2) {
// try {
// graphene::app::history_api hist_api(app);
//
// // account_id_type() is tracked
//
// // account_id_type() creates alice(tracked account)
// const account_object& alice = create_account("alice");
// auto alice_id = alice.id;
//
// //account_id_type() creates some ops
// create_bitasset("CNY", account_id_type());
// create_bitasset("USD", account_id_type());
//
// // alice makes 1 op
// create_bitasset("EUR", alice_id);
//
// // account_id_type() creates dan(account not tracked)
// const account_object& dan = create_account("dan");
// auto dan_id = dan.id;
//
// generate_block();
//
// // all account_id_type() should have 4 ops {4,2,1,0}
// vector<operation_history_object> histories = hist_api.get_account_history(account_id_type(), operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 4u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 4u);
// BOOST_CHECK_EQUAL(histories[1].id.instance(), 2u);
// BOOST_CHECK_EQUAL(histories[2].id.instance(), 1u);
// BOOST_CHECK_EQUAL(histories[3].id.instance(), 0u);
//
// // all alice account should have 2 ops {3, 0}
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 2u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);
// BOOST_CHECK_EQUAL(histories[1].id.instance(), 0u);
//
// // alice first op should be {0}
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(0), 1, operation_history_id_type(1));
// BOOST_CHECK_EQUAL(histories.size(), 1u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 0u);
//
// // alice second op should be {3}
// histories = hist_api.get_account_history(alice_id, operation_history_id_type(1), 1, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 1u);
// BOOST_CHECK_EQUAL(histories[0].id.instance(), 3u);
//
// // anything against dan should be {}
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(0), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 10, operation_history_id_type(0));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
// histories = hist_api.get_account_history(dan_id, operation_history_id_type(1), 1, operation_history_id_type(2));
// BOOST_CHECK_EQUAL(histories.size(), 0u);
//
// } catch (fc::exception &e) {
// edump((e.to_detail_string()));
// throw;
// }
//}
BOOST_AUTO_TEST_CASE(get_account_history_operations) {
try {