peerplays_migrated/libraries/wallet/wallet.cpp
Daniel Larimer 7e142eb4d6 Wallet Blind Transfer Implementation
Improve Pretty Printing of Operations
Fix bug preventing wallet from being unlocked after saving while locked.
Transfer from public to blind
Transfer from blind to blind
Transfer from blind to public
Blind Account History
2015-07-24 17:02:06 -04:00

3189 lines
123 KiB
C++

/*
* Copyright (c) 2015, Cryptonomex, Incn
* All rights reserved.
*
* This source code is provided for evaluation in private test networks only, until September 8, 2015. After this date, this license expires and
* the code may not be used, modified or distributed for any purpose. Redistribution and use in source and binary forms, with or without modification,
* are permitted until September 8, 2015, provided that the following conditions are met:
*
* 1. The code and/or derivative works are used only for private test networks consisting of no more than 10 P2P nodes.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
* PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
* WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
* ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#include <algorithm>
#include <cctype>
#include <iomanip>
#include <iostream>
#include <iterator>
#include <sstream>
#include <string>
#include <list>
#include <boost/range/adaptor/map.hpp>
#include <boost/range/algorithm_ext/erase.hpp>
#include <boost/range/algorithm/unique.hpp>
#include <boost/range/algorithm/sort.hpp>
#include <boost/multi_index_container.hpp>
#include <boost/multi_index/ordered_index.hpp>
#include <boost/multi_index/mem_fun.hpp>
#include <boost/multi_index/member.hpp>
#include <boost/multi_index/random_access_index.hpp>
#include <boost/multi_index/tag.hpp>
#include <boost/multi_index/sequenced_index.hpp>
#include <boost/multi_index/hashed_index.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/json.hpp>
#include <fc/io/stdio.hpp>
#include <fc/network/http/websocket.hpp>
#include <fc/rpc/cli.hpp>
#include <fc/rpc/websocket_api.hpp>
#include <fc/crypto/aes.hpp>
#include <fc/crypto/hex.hpp>
#include <fc/thread/mutex.hpp>
#include <fc/thread/scoped_lock.hpp>
#include <graphene/app/api.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <graphene/utilities/words.hpp>
#include <graphene/wallet/wallet.hpp>
#include <graphene/wallet/api_documentation.hpp>
#include <fc/smart_ref_impl.hpp>
#ifndef WIN32
# include <sys/types.h>
# include <sys/stat.h>
#endif
#define BRAIN_KEY_WORD_COUNT 16
namespace graphene { namespace wallet {
namespace detail {
// BLOCK TRX OP VOP
struct operation_printer
{
private:
ostream& out;
const wallet_api_impl& wallet;
operation_result result;
void fee(const asset& a) const;
public:
operation_printer( ostream& out, const wallet_api_impl& wallet, const operation_result& r = operation_result() )
: out(out),
wallet(wallet),
result(r)
{}
typedef void result_type;
template<typename T>
void operator()(const T& op)const;
void operator()(const transfer_operation& op)const;
void operator()(const transfer_from_blind_operation& op)const;
void operator()(const transfer_to_blind_operation& op)const;
void operator()(const account_create_operation& op)const;
void operator()(const account_update_operation& op)const;
void operator()(const asset_create_operation& op)const;
};
template<class T>
optional<T> maybe_id( const string& name_or_id )
{
if( std::isdigit( name_or_id.front() ) )
{
try
{
return fc::variant(name_or_id).as<T>();
}
catch (const fc::exception&)
{
}
}
return optional<T>();
}
string address_to_shorthash( const address& addr )
{
uint32_t x = addr.addr._hash[0];
static const char hd[] = "0123456789abcdef";
string result;
result += hd[(x >> 0x1c) & 0x0f];
result += hd[(x >> 0x18) & 0x0f];
result += hd[(x >> 0x14) & 0x0f];
result += hd[(x >> 0x10) & 0x0f];
result += hd[(x >> 0x0c) & 0x0f];
result += hd[(x >> 0x08) & 0x0f];
result += hd[(x >> 0x04) & 0x0f];
result += hd[(x ) & 0x0f];
return result;
}
fc::ecc::private_key derive_private_key( const std::string& prefix_string,
int sequence_number )
{
std::string sequence_string = std::to_string(sequence_number);
fc::sha512 h = fc::sha512::hash(prefix_string + " " + sequence_string);
fc::ecc::private_key derived_key = fc::ecc::private_key::regenerate(fc::sha256::hash(h));
return derived_key;
}
string normalize_brain_key( string s )
{
size_t i = 0, n = s.length();
std::string result;
char c;
result.reserve( n );
bool preceded_by_whitespace = false;
bool non_empty = false;
while( i < n )
{
c = s[i++];
switch( c )
{
case ' ': case '\t': case '\r': case '\n': case '\v': case '\f':
preceded_by_whitespace = true;
continue;
case 'a': c = 'A'; break;
case 'b': c = 'B'; break;
case 'c': c = 'C'; break;
case 'd': c = 'D'; break;
case 'e': c = 'E'; break;
case 'f': c = 'F'; break;
case 'g': c = 'G'; break;
case 'h': c = 'H'; break;
case 'i': c = 'I'; break;
case 'j': c = 'J'; break;
case 'k': c = 'K'; break;
case 'l': c = 'L'; break;
case 'm': c = 'M'; break;
case 'n': c = 'N'; break;
case 'o': c = 'O'; break;
case 'p': c = 'P'; break;
case 'q': c = 'Q'; break;
case 'r': c = 'R'; break;
case 's': c = 'S'; break;
case 't': c = 'T'; break;
case 'u': c = 'U'; break;
case 'v': c = 'V'; break;
case 'w': c = 'W'; break;
case 'x': c = 'X'; break;
case 'y': c = 'Y'; break;
case 'z': c = 'Z'; break;
default:
break;
}
if( preceded_by_whitespace && non_empty )
result.push_back(' ');
result.push_back(c);
preceded_by_whitespace = false;
non_empty = true;
}
return result;
}
struct op_prototype_visitor
{
typedef void result_type;
int t = 0;
flat_map< std::string, operation >& name2op;
op_prototype_visitor(
int _t,
flat_map< std::string, operation >& _prototype_ops
):t(_t), name2op(_prototype_ops) {}
template<typename Type>
result_type operator()( const Type& op )const
{
string name = fc::get_typename<Type>::name();
size_t p = name.rfind(':');
if( p != string::npos )
name = name.substr( p+1 );
name2op[ name ] = Type();
}
};
class wallet_api_impl
{
public:
api_documentation method_documentation;
private:
void claim_registered_account(const account_object& account)
{
auto it = _wallet.pending_account_registrations.find( account.name );
FC_ASSERT( it != _wallet.pending_account_registrations.end() );
for (const std::string& wif_key : it->second)
if( !import_key( account.name, wif_key ) )
{
// somebody else beat our pending registration, there is
// nothing we can do except log it and move on
elog( "account ${name} registered by someone else first!",
("name", account.name) );
// might as well remove it from pending regs,
// because there is now no way this registration
// can become valid (even in the extremely rare
// possibility of migrating to a fork where the
// name is available, the user can always
// manually re-register)
}
_wallet.pending_account_registrations.erase( it );
}
// after a witness registration succeeds, this saves the private key in the wallet permanently
//
void claim_registered_witness(const std::string& witness_name)
{
auto iter = _wallet.pending_witness_registrations.find(witness_name);
FC_ASSERT(iter != _wallet.pending_witness_registrations.end());
std::string wif_key = iter->second;
// get the list key id this key is registered with in the chain
fc::optional<fc::ecc::private_key> witness_private_key = wif_to_key(wif_key);
FC_ASSERT(witness_private_key);
auto pub_key = witness_private_key->get_public_key();
_keys[pub_key] = wif_key;
_wallet.pending_witness_registrations.erase(iter);
}
fc::mutex _resync_mutex;
void resync()
{
fc::scoped_lock<fc::mutex> lock(_resync_mutex);
// this method is used to update wallet_data annotations
// e.g. wallet has been restarted and was not notified
// of events while it was down
//
// everything that is done "incremental style" when a push
// notification is received, should also be done here
// "batch style" by querying the blockchain
if( !_wallet.pending_account_registrations.empty() )
{
// make a vector of the account names pending registration
std::vector<string> pending_account_names = boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_account_registrations));
// look those up on the blockchain
std::vector<fc::optional<graphene::chain::account_object >>
pending_account_objects = _remote_db->lookup_account_names( pending_account_names );
// if any of them exist, claim them
for( const fc::optional<graphene::chain::account_object>& optional_account : pending_account_objects )
if( optional_account )
claim_registered_account(*optional_account);
}
if (!_wallet.pending_witness_registrations.empty())
{
// make a vector of the owner accounts for witnesses pending registration
std::vector<string> pending_witness_names = boost::copy_range<std::vector<string> >(boost::adaptors::keys(_wallet.pending_witness_registrations));
// look up the owners on the blockchain
std::vector<fc::optional<graphene::chain::account_object>> owner_account_objects = _remote_db->lookup_account_names(pending_witness_names);
// if any of them have registered witnesses, claim them
for( const fc::optional<graphene::chain::account_object>& optional_account : owner_account_objects )
if (optional_account)
{
fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(optional_account->id);
if (witness_obj)
claim_registered_witness(optional_account->name);
}
}
}
void enable_umask_protection()
{
#ifdef __unix__
_old_umask = umask( S_IRWXG | S_IRWXO );
#endif
}
void disable_umask_protection()
{
#ifdef __unix__
umask( _old_umask );
#endif
}
void init_prototype_ops()
{
operation op;
for( int t=0; t<op.count(); t++ )
{
op.set_which( t );
op.visit( op_prototype_visitor(t, _prototype_ops) );
}
return;
}
map<transaction_handle_type, signed_transaction> _builder_transactions;
// if the user executes the same command twice in quick succession,
// we might generate the same transaction id, and cause the second
// transaction to be rejected. This can be avoided by altering the
// second transaction slightly (bumping up the expiration time by
// a second). Keep track of recent transaction ids we've generated
// so we can know if we need to do this
struct recently_generated_transaction_record
{
fc::time_point_sec generation_time;
graphene::chain::transaction_id_type transaction_id;
};
struct timestamp_index{};
typedef boost::multi_index_container<recently_generated_transaction_record,
boost::multi_index::indexed_by<boost::multi_index::hashed_unique<boost::multi_index::member<recently_generated_transaction_record,
graphene::chain::transaction_id_type,
&recently_generated_transaction_record::transaction_id>,
std::hash<graphene::chain::transaction_id_type> >,
boost::multi_index::ordered_non_unique<boost::multi_index::tag<timestamp_index>,
boost::multi_index::member<recently_generated_transaction_record, fc::time_point_sec, &recently_generated_transaction_record::generation_time> > > > recently_generated_transaction_set_type;
recently_generated_transaction_set_type _recently_generated_transactions;
public:
wallet_api& self;
wallet_api_impl( wallet_api& s, fc::api<login_api> rapi )
: self(s),
_remote_api(rapi),
_remote_db(rapi->database()),
_remote_net_broadcast(rapi->network_broadcast()),
_remote_hist(rapi->history())
{
init_prototype_ops();
_remote_db->subscribe_to_objects( [=]( const fc::variant& obj )
{
fc::async([this]{resync();}, "Resync after block");
}, {dynamic_global_property_id_type()} );
}
virtual ~wallet_api_impl()
{
try
{
_remote_db->cancel_all_subscriptions();
}
catch (const fc::exception& e)
{
// Right now the wallet_api has no way of knowing if the connection to the
// witness has already disconnected (via the witness node exiting first).
// If it has exited, cancel_all_subscriptsions() will throw and there's
// nothing we can do about it.
// dlog("Caught exception ${e} while canceling database subscriptions", ("e", e));
}
}
void encrypt_keys()
{
if( !is_locked() )
{
plain_keys data;
data.keys = _keys;
data.checksum = _checksum;
auto plain_txt = fc::raw::pack(data);
_wallet.cipher_keys = fc::aes_encrypt( data.checksum, plain_txt );
}
}
bool copy_wallet_file( string destination_filename )
{
fc::path src_path = get_wallet_filename();
if( !fc::exists( src_path ) )
return false;
fc::path dest_path = destination_filename + _wallet_filename_extension;
int suffix = 0;
while( fc::exists(dest_path) )
{
++suffix;
dest_path = destination_filename + "-" + to_string( suffix ) + _wallet_filename_extension;
}
wlog( "backing up wallet ${src} to ${dest}",
("src", src_path)
("dest", dest_path) );
fc::path dest_parent = fc::absolute(dest_path).parent_path();
try
{
enable_umask_protection();
if( !fc::exists( dest_parent ) )
fc::create_directories( dest_parent );
fc::copy( src_path, dest_path );
disable_umask_protection();
}
catch(...)
{
disable_umask_protection();
throw;
}
return true;
}
bool is_locked()const
{
return _checksum == fc::sha512();
}
template<typename T>
T get_object(object_id<T::space_id, T::type_id, T> id)const
{
auto ob = _remote_db->get_objects({id}).front();
return ob.template as<T>();
}
void set_operation_fees( signed_transaction& tx, const fee_schedule& s )
{
for( auto& op : tx.operations )
s.set_fee(op);
}
variant info() const
{
auto global_props = get_global_properties();
auto dynamic_props = get_dynamic_global_properties();
fc::mutable_variant_object result;
result["head_block_num"] = dynamic_props.head_block_number;
result["head_block_id"] = dynamic_props.head_block_id;
result["head_block_age"] = fc::get_approximate_relative_time_string(dynamic_props.time,
time_point_sec(time_point::now()),
" old");
result["next_maintenance_time"] = fc::get_approximate_relative_time_string(dynamic_props.next_maintenance_time);
result["chain_id"] = global_props.chain_id;
result["active_witnesses"] = global_props.active_witnesses;
result["active_committee_members"] = global_props.active_committee_members;
result["entropy"] = dynamic_props.random;
return result;
}
global_property_object get_global_properties() const
{
return _remote_db->get_global_properties();
}
dynamic_global_property_object get_dynamic_global_properties() const
{
return _remote_db->get_dynamic_global_properties();
}
account_object get_account(account_id_type id) const
{
if( _wallet.my_accounts.get<by_id>().count(id) )
return *_wallet.my_accounts.get<by_id>().find(id);
auto rec = _remote_db->get_accounts({id}).front();
FC_ASSERT(rec);
return *rec;
}
account_object get_account(string account_name_or_id) const
{
FC_ASSERT( account_name_or_id.size() > 0 );
if( auto id = maybe_id<account_id_type>(account_name_or_id) )
{
// It's an ID
return get_account(*id);
} else {
// It's a name
if( _wallet.my_accounts.get<by_name>().count(account_name_or_id) )
{
auto local_account = *_wallet.my_accounts.get<by_name>().find(account_name_or_id);
auto blockchain_account = _remote_db->lookup_account_names({account_name_or_id}).front();
FC_ASSERT( blockchain_account );
if (local_account.id != blockchain_account->id)
elog("my account id ${id} different from blockchain id ${id2}", ("id", local_account.id)("id2", blockchain_account->id));
if (local_account.name != blockchain_account->name)
elog("my account name ${id} different from blockchain name ${id2}", ("id", local_account.name)("id2", blockchain_account->name));
return *_wallet.my_accounts.get<by_name>().find(account_name_or_id);
}
auto rec = _remote_db->lookup_account_names({account_name_or_id}).front();
FC_ASSERT( rec && rec->name == account_name_or_id );
return *rec;
}
}
account_id_type get_account_id(string account_name_or_id) const
{
return get_account(account_name_or_id).get_id();
}
optional<asset_object> find_asset(asset_id_type id)const
{
auto rec = _remote_db->get_assets({id}).front();
if( rec )
_asset_cache[id] = *rec;
return rec;
}
optional<asset_object> find_asset(string asset_symbol_or_id)const
{
FC_ASSERT( asset_symbol_or_id.size() > 0 );
if( auto id = maybe_id<asset_id_type>(asset_symbol_or_id) )
{
// It's an ID
return find_asset(*id);
} else {
// It's a symbol
auto rec = _remote_db->lookup_asset_symbols({asset_symbol_or_id}).front();
if( rec )
{
if( rec->symbol != asset_symbol_or_id )
return optional<asset_object>();
_asset_cache[rec->get_id()] = *rec;
}
return rec;
}
}
asset_object get_asset(asset_id_type id)const
{
auto opt = find_asset(id);
FC_ASSERT(opt);
return *opt;
}
asset_object get_asset(string asset_symbol_or_id)const
{
auto opt = find_asset(asset_symbol_or_id);
FC_ASSERT(opt);
return *opt;
}
asset_id_type get_asset_id(string asset_symbol_or_id) const
{
FC_ASSERT( asset_symbol_or_id.size() > 0 );
vector<optional<asset_object>> opt_asset;
if( std::isdigit( asset_symbol_or_id.front() ) )
return fc::variant(asset_symbol_or_id).as<asset_id_type>();
opt_asset = _remote_db->lookup_asset_symbols( {asset_symbol_or_id} );
FC_ASSERT( (opt_asset.size() > 0) && (opt_asset[0].valid()) );
return opt_asset[0]->id;
}
string get_wallet_filename() const
{
return _wallet_filename;
}
fc::ecc::private_key get_private_key(const public_key_type& id)const
{
auto it = _keys.find(id);
FC_ASSERT( it != _keys.end() );
fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );
FC_ASSERT( privkey );
return *privkey;
}
fc::ecc::private_key get_private_key_for_account(const account_object& account)const
{
vector<public_key_type> active_keys = account.active.get_keys();
if (active_keys.size() != 1)
FC_THROW("Expecting a simple authority with one active key");
return get_private_key(active_keys.front());
}
// imports the private key into the wallet, and associate it in some way (?) with the
// given account name.
// @returns true if the key matches a current active/owner/memo key for the named
// account, false otherwise (but it is stored either way)
bool import_key(string account_name_or_id, string wif_key)
{
fc::optional<fc::ecc::private_key> optional_private_key = wif_to_key(wif_key);
if (!optional_private_key)
FC_THROW("Invalid private key ${key}", ("key", wif_key));
graphene::chain::public_key_type wif_pub_key = optional_private_key->get_public_key();
account_object account = get_account( account_name_or_id );
// make a list of all current public keys for the named account
flat_set<public_key_type> all_keys_for_account;
std::vector<public_key_type> active_keys = account.active.get_keys();
std::vector<public_key_type> owner_keys = account.owner.get_keys();
std::copy(active_keys.begin(), active_keys.end(), std::inserter(all_keys_for_account, all_keys_for_account.end()));
std::copy(owner_keys.begin(), owner_keys.end(), std::inserter(all_keys_for_account, all_keys_for_account.end()));
all_keys_for_account.insert(account.options.memo_key);
_keys[wif_pub_key] = wif_key;
if( _wallet.update_account(account) )
_remote_db->subscribe_to_objects([this](const fc::variant& v) {
_wallet.update_account(v.as<account_object>());
}, {account.id});
_wallet.extra_keys[account.id].insert(wif_pub_key);
return all_keys_for_account.find(wif_pub_key) != all_keys_for_account.end();
}
bool load_wallet_file(string wallet_filename = "")
{
// TODO: Merge imported wallet with existing wallet,
// instead of replacing it
if( wallet_filename == "" )
wallet_filename = _wallet_filename;
if( ! fc::exists( wallet_filename ) )
return false;
if( !_wallet.my_accounts.empty() )
_remote_db->unsubscribe_from_objects(_wallet.my_account_ids());
_wallet = fc::json::from_file( wallet_filename ).as< wallet_data >();
if( !_wallet.my_accounts.empty() )
_remote_db->subscribe_to_objects([this](const fc::variant& v) {
_wallet.update_account(v.as<account_object>());
}, _wallet.my_account_ids());
return true;
}
void save_wallet_file(string wallet_filename = "")
{
//
// Serialize in memory, then save to disk
//
// This approach lessens the risk of a partially written wallet
// if exceptions are thrown in serialization
//
encrypt_keys();
if( wallet_filename == "" )
wallet_filename = _wallet_filename;
wlog( "saving wallet to file ${fn}", ("fn", wallet_filename) );
string data = fc::json::to_pretty_string( _wallet );
try
{
enable_umask_protection();
//
// Parentheses on the following declaration fails to compile,
// due to the Most Vexing Parse. Thanks, C++
//
// http://en.wikipedia.org/wiki/Most_vexing_parse
//
fc::ofstream outfile{ fc::path( wallet_filename ) };
outfile.write( data.c_str(), data.length() );
outfile.flush();
outfile.close();
disable_umask_protection();
}
catch(...)
{
disable_umask_protection();
throw;
}
}
transaction_handle_type begin_builder_transaction()
{
int trx_handle = _builder_transactions.empty()? 0
: (--_builder_transactions.end())->first + 1;
_builder_transactions[trx_handle];
return trx_handle;
}
void add_operation_to_builder_transaction(transaction_handle_type transaction_handle, const operation& op)
{
FC_ASSERT(_builder_transactions.count(transaction_handle));
_builder_transactions[transaction_handle].operations.emplace_back(op);
}
void replace_operation_in_builder_transaction(transaction_handle_type handle,
uint32_t operation_index,
const operation& new_op)
{
FC_ASSERT(_builder_transactions.count(handle));
signed_transaction& trx = _builder_transactions[handle];
FC_ASSERT( operation_index < trx.operations.size());
trx.operations[operation_index] = new_op;
}
asset set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset = GRAPHENE_SYMBOL)
{
FC_ASSERT(_builder_transactions.count(handle));
auto fee_asset_obj = get_asset(fee_asset);
asset total_fee = fee_asset_obj.amount(0);
auto gprops = _remote_db->get_global_properties().parameters;
if( fee_asset_obj.get_id() != asset_id_type() )
{
for( auto& op : _builder_transactions[handle].operations )
total_fee += gprops.current_fees->set_fee( op, fee_asset_obj.options.core_exchange_rate );
FC_ASSERT((total_fee * fee_asset_obj.options.core_exchange_rate).amount <=
get_object<asset_dynamic_data_object>(fee_asset_obj.dynamic_asset_data_id).fee_pool,
"Cannot pay fees in ${asset}, as this asset's fee pool is insufficiently funded.",
("asset", fee_asset_obj.symbol));
} else {
for( auto& op : _builder_transactions[handle].operations )
total_fee += gprops.current_fees->set_fee( op );
}
return total_fee;
}
transaction preview_builder_transaction(transaction_handle_type handle)
{
FC_ASSERT(_builder_transactions.count(handle));
return _builder_transactions[handle];
}
signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true)
{
FC_ASSERT(_builder_transactions.count(transaction_handle));
return _builder_transactions[transaction_handle] = sign_transaction(_builder_transactions[transaction_handle], broadcast);
}
signed_transaction propose_builder_transaction(transaction_handle_type handle,
time_point_sec expiration = time_point::now() + fc::minutes(1),
uint32_t review_period_seconds = 0, bool broadcast = true)
{
FC_ASSERT(_builder_transactions.count(handle));
proposal_create_operation op;
op.expiration_time = expiration;
signed_transaction& trx = _builder_transactions[handle];
std::transform(trx.operations.begin(), trx.operations.end(), std::back_inserter(op.proposed_ops),
[](const operation& op) -> op_wrapper { return op; });
if( review_period_seconds )
op.review_period_seconds = review_period_seconds;
trx.operations = {op};
_remote_db->get_global_properties().parameters.current_fees->set_fee( trx.operations.front() );
return trx = sign_transaction(trx, broadcast);
}
void remove_builder_transaction(transaction_handle_type handle)
{
_builder_transactions.erase(handle);
}
signed_transaction register_account(string name,
public_key_type owner,
public_key_type active,
string registrar_account,
string referrer_account,
uint8_t referrer_percent,
bool broadcast = false)
{ try {
FC_ASSERT( !self.is_locked() );
FC_ASSERT( is_valid_name(name) );
account_create_operation account_create_op;
// TODO: process when pay_from_account is ID
account_object registrar_account_object =
this->get_account( registrar_account );
FC_ASSERT( registrar_account_object.is_lifetime_member() );
account_id_type registrar_account_id = registrar_account_object.id;
account_object referrer_account_object =
this->get_account( referrer_account );
account_create_op.referrer = referrer_account_object.id;
account_create_op.referrer_percent = referrer_percent;
account_create_op.registrar = registrar_account_id;
account_create_op.name = name;
account_create_op.owner = authority(1, owner, 1);
account_create_op.active = authority(1, active, 1);
account_create_op.options.memo_key = active;
signed_transaction tx;
tx.operations.push_back( account_create_op );
auto current_fees = _remote_db->get_global_properties().parameters.current_fees;
set_operation_fees( tx, current_fees );
vector<public_key_type> paying_keys = registrar_account_object.active.get_keys();
auto dyn_props = get_dynamic_global_properties();
tx.set_reference_block( dyn_props.head_block_id );
tx.set_expiration( dyn_props.time + fc::seconds(30) );
tx.validate();
for( public_key_type& key : paying_keys )
{
auto it = _keys.find(key);
if( it != _keys.end() )
{
fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );
if( !privkey.valid() )
{
FC_ASSERT( false, "Malformed private key in _keys" );
}
tx.sign( *privkey );
}
}
if( broadcast )
_remote_net_broadcast->broadcast_transaction( tx );
return tx;
} FC_CAPTURE_AND_RETHROW( (name)(owner)(active)(registrar_account)(referrer_account)(referrer_percent)(broadcast) ) }
signed_transaction upgrade_account(string name, bool broadcast)
{ try {
FC_ASSERT( !self.is_locked() );
account_object account_obj = get_account(name);
FC_ASSERT( !account_obj.is_lifetime_member() );
signed_transaction tx;
account_upgrade_operation op;
op.account_to_upgrade = account_obj.get_id();
op.upgrade_to_lifetime_member = true;
tx.operations = {op};
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees );
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (name) ) }
// This function generates derived keys starting with index 0 and keeps incrementing
// the index until it finds a key that isn't registered in the block chain. To be
// safer, it continues checking for a few more keys to make sure there wasn't a short gap
// caused by a failed registration or the like.
int find_first_unused_derived_key_index(const fc::ecc::private_key& parent_key)
{
int first_unused_index = 0;
int number_of_consecutive_unused_keys = 0;
for (int key_index = 0; ; ++key_index)
{
fc::ecc::private_key derived_private_key = derive_private_key(key_to_wif(parent_key), key_index);
graphene::chain::public_key_type derived_public_key = derived_private_key.get_public_key();
if( _keys.find(derived_public_key) == _keys.end() )
{
if (number_of_consecutive_unused_keys)
{
++number_of_consecutive_unused_keys;
if (number_of_consecutive_unused_keys > 5)
return first_unused_index;
}
else
{
first_unused_index = key_index;
number_of_consecutive_unused_keys = 1;
}
}
else
{
// key_index is used
first_unused_index = 0;
number_of_consecutive_unused_keys = 0;
}
}
}
signed_transaction create_account_with_private_key(fc::ecc::private_key owner_privkey,
string account_name,
string registrar_account,
string referrer_account,
bool broadcast = false,
bool save_wallet = true)
{ try {
int active_key_index = find_first_unused_derived_key_index(owner_privkey);
fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), active_key_index);
int memo_key_index = find_first_unused_derived_key_index(active_privkey);
fc::ecc::private_key memo_privkey = derive_private_key( key_to_wif(active_privkey), memo_key_index);
graphene::chain::public_key_type owner_pubkey = owner_privkey.get_public_key();
graphene::chain::public_key_type active_pubkey = active_privkey.get_public_key();
graphene::chain::public_key_type memo_pubkey = memo_privkey.get_public_key();
account_create_operation account_create_op;
// TODO: process when pay_from_account is ID
account_object registrar_account_object = get_account( registrar_account );
account_id_type registrar_account_id = registrar_account_object.id;
account_object referrer_account_object = get_account( referrer_account );
account_create_op.referrer = referrer_account_object.id;
account_create_op.referrer_percent = referrer_account_object.referrer_rewards_percentage;
account_create_op.registrar = registrar_account_id;
account_create_op.name = account_name;
account_create_op.owner = authority(1, owner_pubkey, 1);
account_create_op.active = authority(1, active_pubkey, 1);
account_create_op.options.memo_key = memo_pubkey;
// current_fee_schedule()
// find_account(pay_from_account)
// account_create_op.fee = account_create_op.calculate_fee(db.current_fee_schedule());
signed_transaction tx;
tx.operations.push_back( account_create_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
vector<public_key_type> paying_keys = registrar_account_object.active.get_keys();
auto dyn_props = get_dynamic_global_properties();
tx.set_reference_block( dyn_props.head_block_id );
tx.set_expiration( dyn_props.time + fc::seconds(30) );
tx.validate();
for( public_key_type& key : paying_keys )
{
auto it = _keys.find(key);
if( it != _keys.end() )
{
fc::optional< fc::ecc::private_key > privkey = wif_to_key( it->second );
FC_ASSERT( privkey.valid(), "Malformed private key in _keys" );
tx.sign( *privkey );
}
}
// we do not insert owner_privkey here because
// it is intended to only be used for key recovery
_wallet.pending_account_registrations[account_name].push_back(key_to_wif( active_privkey ));
_wallet.pending_account_registrations[account_name].push_back(key_to_wif( memo_privkey ));
if( save_wallet )
save_wallet_file();
if( broadcast )
_remote_net_broadcast->broadcast_transaction( tx );
return tx;
} FC_CAPTURE_AND_RETHROW( (account_name)(registrar_account)(referrer_account)(broadcast) ) }
signed_transaction create_account_with_brain_key(string brain_key,
string account_name,
string registrar_account,
string referrer_account,
bool broadcast = false,
bool save_wallet = true)
{ try {
FC_ASSERT( !self.is_locked() );
string normalized_brain_key = normalize_brain_key( brain_key );
// TODO: scan blockchain for accounts that exist with same brain key
fc::ecc::private_key owner_privkey = derive_private_key( normalized_brain_key, 0 );
return create_account_with_private_key(owner_privkey, account_name, registrar_account, referrer_account, broadcast, save_wallet);
} FC_CAPTURE_AND_RETHROW( (account_name)(registrar_account)(referrer_account) ) }
signed_transaction create_asset(string issuer,
string symbol,
uint8_t precision,
asset_options common,
fc::optional<bitasset_options> bitasset_opts,
bool broadcast = false)
{ try {
account_object issuer_account = get_account( issuer );
FC_ASSERT(!find_asset(symbol).valid(), "Asset with that symbol already exists!");
asset_create_operation create_op;
create_op.issuer = issuer_account.id;
create_op.symbol = symbol;
create_op.precision = precision;
create_op.common_options = common;
create_op.bitasset_opts = bitasset_opts;
signed_transaction tx;
tx.operations.push_back( create_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (issuer)(symbol)(precision)(common)(bitasset_opts)(broadcast) ) }
signed_transaction update_asset(string symbol,
optional<string> new_issuer,
asset_options new_options,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_update = find_asset(symbol);
if (!asset_to_update)
FC_THROW("No asset with that symbol exists!");
optional<account_id_type> new_issuer_account_id;
if (new_issuer)
{
account_object new_issuer_account = get_account(*new_issuer);
new_issuer_account_id = new_issuer_account.id;
}
asset_update_operation update_op;
update_op.issuer = asset_to_update->issuer;
update_op.asset_to_update = asset_to_update->id;
update_op.new_issuer = new_issuer_account_id;
update_op.new_options = new_options;
signed_transaction tx;
tx.operations.push_back( update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (symbol)(new_issuer)(new_options)(broadcast) ) }
signed_transaction update_bitasset(string symbol,
bitasset_options new_options,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_update = find_asset(symbol);
if (!asset_to_update)
FC_THROW("No asset with that symbol exists!");
asset_update_bitasset_operation update_op;
update_op.issuer = asset_to_update->issuer;
update_op.asset_to_update = asset_to_update->id;
update_op.new_options = new_options;
signed_transaction tx;
tx.operations.push_back( update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (symbol)(new_options)(broadcast) ) }
signed_transaction update_asset_feed_producers(string symbol,
flat_set<string> new_feed_producers,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_update = find_asset(symbol);
if (!asset_to_update)
FC_THROW("No asset with that symbol exists!");
asset_update_feed_producers_operation update_op;
update_op.issuer = asset_to_update->issuer;
update_op.asset_to_update = asset_to_update->id;
update_op.new_feed_producers.reserve(new_feed_producers.size());
std::transform(new_feed_producers.begin(), new_feed_producers.end(),
std::inserter(update_op.new_feed_producers, update_op.new_feed_producers.end()),
[this](const std::string& account_name_or_id){ return get_account_id(account_name_or_id); });
signed_transaction tx;
tx.operations.push_back( update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (symbol)(new_feed_producers)(broadcast) ) }
signed_transaction publish_asset_feed(string publishing_account,
string symbol,
price_feed feed,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_update = find_asset(symbol);
if (!asset_to_update)
FC_THROW("No asset with that symbol exists!");
asset_publish_feed_operation publish_op;
publish_op.publisher = get_account_id(publishing_account);
publish_op.asset_id = asset_to_update->id;
publish_op.feed = feed;
signed_transaction tx;
tx.operations.push_back( publish_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (publishing_account)(symbol)(feed)(broadcast) ) }
signed_transaction fund_asset_fee_pool(string from,
string symbol,
string amount,
bool broadcast /* = false */)
{ try {
account_object from_account = get_account(from);
optional<asset_object> asset_to_fund = find_asset(symbol);
if (!asset_to_fund)
FC_THROW("No asset with that symbol exists!");
asset_object core_asset = get_asset(asset_id_type());
asset_fund_fee_pool_operation fund_op;
fund_op.from_account = from_account.id;
fund_op.asset_id = asset_to_fund->id;
fund_op.amount = core_asset.amount_from_string(amount).amount;
signed_transaction tx;
tx.operations.push_back( fund_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (from)(symbol)(amount)(broadcast) ) }
signed_transaction reserve_asset(string from,
string amount,
string symbol,
bool broadcast /* = false */)
{ try {
account_object from_account = get_account(from);
optional<asset_object> asset_to_reserve = find_asset(symbol);
if (!asset_to_reserve)
FC_THROW("No asset with that symbol exists!");
asset_reserve_operation reserve_op;
reserve_op.payer = from_account.id;
reserve_op.amount_to_reserve = asset_to_reserve->amount_from_string(amount);
signed_transaction tx;
tx.operations.push_back( reserve_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (from)(amount)(symbol)(broadcast) ) }
signed_transaction global_settle_asset(string symbol,
price settle_price,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_settle = find_asset(symbol);
if (!asset_to_settle)
FC_THROW("No asset with that symbol exists!");
asset_global_settle_operation settle_op;
settle_op.issuer = asset_to_settle->issuer;
settle_op.asset_to_settle = asset_to_settle->id;
settle_op.settle_price = settle_price;
signed_transaction tx;
tx.operations.push_back( settle_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (symbol)(settle_price)(broadcast) ) }
signed_transaction settle_asset(string account_to_settle,
string amount_to_settle,
string symbol,
bool broadcast /* = false */)
{ try {
optional<asset_object> asset_to_settle = find_asset(symbol);
if (!asset_to_settle)
FC_THROW("No asset with that symbol exists!");
asset_settle_operation settle_op;
settle_op.account = get_account_id(account_to_settle);
settle_op.amount = asset_to_settle->amount_from_string(amount_to_settle);
signed_transaction tx;
tx.operations.push_back( settle_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (account_to_settle)(amount_to_settle)(symbol)(broadcast) ) }
signed_transaction whitelist_account(string authorizing_account,
string account_to_list,
account_whitelist_operation::account_listing new_listing_status,
bool broadcast /* = false */)
{ try {
account_whitelist_operation whitelist_op;
whitelist_op.authorizing_account = get_account_id(authorizing_account);
whitelist_op.account_to_list = get_account_id(account_to_list);
whitelist_op.new_listing = new_listing_status;
signed_transaction tx;
tx.operations.push_back( whitelist_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (authorizing_account)(account_to_list)(new_listing_status)(broadcast) ) }
signed_transaction create_committee_member(string owner_account, string url,
bool broadcast /* = false */)
{ try {
committee_member_create_operation committee_member_create_op;
committee_member_create_op.committee_member_account = get_account_id(owner_account);
committee_member_create_op.url = url;
if (_remote_db->get_committee_member_by_account(committee_member_create_op.committee_member_account))
FC_THROW("Account ${owner_account} is already a committee_member", ("owner_account", owner_account));
signed_transaction tx;
tx.operations.push_back( committee_member_create_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) }
witness_object get_witness(string owner_account)
{
try
{
fc::optional<witness_id_type> witness_id = maybe_id<witness_id_type>(owner_account);
if (witness_id)
{
std::vector<witness_id_type> ids_to_get;
ids_to_get.push_back(*witness_id);
std::vector<fc::optional<witness_object>> witness_objects = _remote_db->get_witnesses(ids_to_get);
if (witness_objects.front())
return *witness_objects.front();
FC_THROW("No witness is registered for id ${id}", ("id", owner_account));
}
else
{
// then maybe it's the owner account
try
{
account_id_type owner_account_id = get_account_id(owner_account);
fc::optional<witness_object> witness = _remote_db->get_witness_by_account(owner_account_id);
if (witness)
return *witness;
else
FC_THROW("No witness is registered for account ${account}", ("account", owner_account));
}
catch (const fc::exception&)
{
FC_THROW("No account or witness named ${account}", ("account", owner_account));
}
}
}
FC_CAPTURE_AND_RETHROW( (owner_account) )
}
committee_member_object get_committee_member(string owner_account)
{
try
{
fc::optional<committee_member_id_type> committee_member_id = maybe_id<committee_member_id_type>(owner_account);
if (committee_member_id)
{
std::vector<committee_member_id_type> ids_to_get;
ids_to_get.push_back(*committee_member_id);
std::vector<fc::optional<committee_member_object>> committee_member_objects = _remote_db->get_committee_members(ids_to_get);
if (committee_member_objects.front())
return *committee_member_objects.front();
FC_THROW("No committee_member is registered for id ${id}", ("id", owner_account));
}
else
{
// then maybe it's the owner account
try
{
account_id_type owner_account_id = get_account_id(owner_account);
fc::optional<committee_member_object> committee_member = _remote_db->get_committee_member_by_account(owner_account_id);
if (committee_member)
return *committee_member;
else
FC_THROW("No committee_member is registered for account ${account}", ("account", owner_account));
}
catch (const fc::exception&)
{
FC_THROW("No account or committee_member named ${account}", ("account", owner_account));
}
}
}
FC_CAPTURE_AND_RETHROW( (owner_account) )
}
signed_transaction create_witness(string owner_account,
string url,
bool broadcast /* = false */)
{ try {
account_object witness_account = get_account(owner_account);
fc::ecc::private_key active_private_key = get_private_key_for_account(witness_account);
int witness_key_index = find_first_unused_derived_key_index(active_private_key);
fc::ecc::private_key witness_private_key = derive_private_key(key_to_wif(active_private_key), witness_key_index);
graphene::chain::public_key_type witness_public_key = witness_private_key.get_public_key();
witness_create_operation witness_create_op;
witness_create_op.witness_account = witness_account.id;
witness_create_op.block_signing_key = witness_public_key;
witness_create_op.url = url;
secret_hash_type::encoder enc;
fc::raw::pack(enc, witness_private_key);
fc::raw::pack(enc, secret_hash_type());
witness_create_op.initial_secret = secret_hash_type::hash(enc.result());
if (_remote_db->get_witness_by_account(witness_create_op.witness_account))
FC_THROW("Account ${owner_account} is already a witness", ("owner_account", owner_account));
signed_transaction tx;
tx.operations.push_back( witness_create_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
_wallet.pending_witness_registrations[owner_account] = key_to_wif(witness_private_key);
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (owner_account)(broadcast) ) }
signed_transaction vote_for_committee_member(string voting_account,
string committee_member,
bool approve,
bool broadcast /* = false */)
{ try {
account_object voting_account_object = get_account(voting_account);
account_id_type committee_member_owner_account_id = get_account_id(committee_member);
fc::optional<committee_member_object> committee_member_obj = _remote_db->get_committee_member_by_account(committee_member_owner_account_id);
if (!committee_member_obj)
FC_THROW("Account ${committee_member} is not registered as a committee_member", ("committee_member", committee_member));
if (approve)
{
auto insert_result = voting_account_object.options.votes.insert(committee_member_obj->vote_id);
if (!insert_result.second)
FC_THROW("Account ${account} was already voting for committee_member ${committee_member}", ("account", voting_account)("committee_member", committee_member));
}
else
{
unsigned votes_removed = voting_account_object.options.votes.erase(committee_member_obj->vote_id);
if (!votes_removed)
FC_THROW("Account ${account} is already not voting for committee_member ${committee_member}", ("account", voting_account)("committee_member", committee_member));
}
account_update_operation account_update_op;
account_update_op.account = voting_account_object.id;
account_update_op.new_options = voting_account_object.options;
signed_transaction tx;
tx.operations.push_back( account_update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (voting_account)(committee_member)(approve)(broadcast) ) }
signed_transaction vote_for_witness(string voting_account,
string witness,
bool approve,
bool broadcast /* = false */)
{ try {
account_object voting_account_object = get_account(voting_account);
account_id_type witness_owner_account_id = get_account_id(witness);
fc::optional<witness_object> witness_obj = _remote_db->get_witness_by_account(witness_owner_account_id);
if (!witness_obj)
FC_THROW("Account ${witness} is not registered as a witness", ("witness", witness));
if (approve)
{
auto insert_result = voting_account_object.options.votes.insert(witness_obj->vote_id);
if (!insert_result.second)
FC_THROW("Account ${account} was already voting for witness ${witness}", ("account", voting_account)("witness", witness));
}
else
{
unsigned votes_removed = voting_account_object.options.votes.erase(witness_obj->vote_id);
if (!votes_removed)
FC_THROW("Account ${account} is already not voting for witness ${witness}", ("account", voting_account)("witness", witness));
}
account_update_operation account_update_op;
account_update_op.account = voting_account_object.id;
account_update_op.new_options = voting_account_object.options;
signed_transaction tx;
tx.operations.push_back( account_update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (voting_account)(witness)(approve)(broadcast) ) }
signed_transaction set_voting_proxy(string account_to_modify,
optional<string> voting_account,
bool broadcast /* = false */)
{ try {
account_object account_object_to_modify = get_account(account_to_modify);
if (voting_account)
{
account_id_type new_voting_account_id = get_account_id(*voting_account);
if (account_object_to_modify.options.voting_account == new_voting_account_id)
FC_THROW("Voting proxy for ${account} is already set to ${voter}", ("account", account_to_modify)("voter", *voting_account));
account_object_to_modify.options.voting_account = new_voting_account_id;
}
else
{
if (account_object_to_modify.options.voting_account == account_id_type())
FC_THROW("Account ${account} is already voting for itself", ("account", account_to_modify));
account_object_to_modify.options.voting_account = account_id_type();
}
account_update_operation account_update_op;
account_update_op.account = account_object_to_modify.id;
account_update_op.new_options = account_object_to_modify.options;
signed_transaction tx;
tx.operations.push_back( account_update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (account_to_modify)(voting_account)(broadcast) ) }
signed_transaction set_desired_witness_and_committee_member_count(string account_to_modify,
uint16_t desired_number_of_witnesses,
uint16_t desired_number_of_committee_members,
bool broadcast /* = false */)
{ try {
account_object account_object_to_modify = get_account(account_to_modify);
if (account_object_to_modify.options.num_witness == desired_number_of_witnesses &&
account_object_to_modify.options.num_committee == desired_number_of_committee_members)
FC_THROW("Account ${account} is already voting for ${witnesses} witnesses and ${committee_members} committee_members",
("account", account_to_modify)("witnesses", desired_number_of_witnesses)("committee_members",desired_number_of_witnesses));
account_object_to_modify.options.num_witness = desired_number_of_witnesses;
account_object_to_modify.options.num_committee = desired_number_of_committee_members;
account_update_operation account_update_op;
account_update_op.account = account_object_to_modify.id;
account_update_op.new_options = account_object_to_modify.options;
signed_transaction tx;
tx.operations.push_back( account_update_op );
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
} FC_CAPTURE_AND_RETHROW( (account_to_modify)(desired_number_of_witnesses)(desired_number_of_committee_members)(broadcast) ) }
signed_transaction sign_transaction(signed_transaction tx, bool broadcast = false)
{
flat_set<account_id_type> req_active_approvals;
flat_set<account_id_type> req_owner_approvals;
vector<authority> other_auths;
tx.get_required_authorities( req_active_approvals, req_owner_approvals, other_auths );
for( const auto& auth : other_auths )
for( const auto& a : auth.account_auths )
req_active_approvals.insert(a.first);
// std::merge lets us de-duplicate account_id's that occur in both
// sets, and dump them into a vector (as required by remote_db api)
// at the same time
vector<account_id_type> v_approving_account_ids;
std::merge(req_active_approvals.begin(), req_active_approvals.end(),
req_owner_approvals.begin() , req_owner_approvals.end(),
std::back_inserter(v_approving_account_ids));
/// TODO: fetch the accounts specified via other_auths as well.
vector< optional<account_object> > approving_account_objects =
_remote_db->get_accounts( v_approving_account_ids );
/// TODO: recursively check one layer deeper in the authority tree for keys
FC_ASSERT( approving_account_objects.size() == v_approving_account_ids.size() );
flat_map<account_id_type, account_object*> approving_account_lut;
size_t i = 0;
for( optional<account_object>& approving_acct : approving_account_objects )
{
if( !approving_acct.valid() )
{
wlog( "operation_get_required_auths said approval of non-existing account ${id} was needed",
("id", v_approving_account_ids[i]) );
i++;
continue;
}
approving_account_lut[ approving_acct->id ] = &(*approving_acct);
i++;
}
flat_set<public_key_type> approving_key_set;
for( account_id_type& acct_id : req_active_approvals )
{
const auto it = approving_account_lut.find( acct_id );
if( it == approving_account_lut.end() )
continue;
const account_object* acct = it->second;
vector<public_key_type> v_approving_keys = acct->active.get_keys();
for( const public_key_type& approving_key : v_approving_keys )
approving_key_set.insert( approving_key );
}
for( account_id_type& acct_id : req_owner_approvals )
{
const auto it = approving_account_lut.find( acct_id );
if( it == approving_account_lut.end() )
continue;
const account_object* acct = it->second;
vector<public_key_type> v_approving_keys = acct->owner.get_keys();
for( const public_key_type& approving_key : v_approving_keys )
approving_key_set.insert( approving_key );
}
for( const authority& a : other_auths )
{
for( const auto& k : a.key_auths )
approving_key_set.insert( k.first );
}
auto dyn_props = get_dynamic_global_properties();
tx.set_reference_block( dyn_props.head_block_id );
// first, some bookkeeping, expire old items from _recently_generated_transactions
// since transactions include the head block id, we just need the index for keeping transactions unique
// when there are multiple transactions in the same block. choose a time period that should be at
// least one block long, even in the worst case. 2 minutes ought to be plenty.
fc::time_point_sec oldest_transaction_ids_to_track(dyn_props.time - fc::minutes(2));
auto oldest_transaction_record_iter = _recently_generated_transactions.get<timestamp_index>().lower_bound(oldest_transaction_ids_to_track);
auto begin_iter = _recently_generated_transactions.get<timestamp_index>().begin();
_recently_generated_transactions.get<timestamp_index>().erase(begin_iter, oldest_transaction_record_iter);
uint32_t expiration_time_offset = 0;
for (;;)
{
tx.set_expiration( dyn_props.time + fc::seconds(30 + expiration_time_offset) );
tx.signatures.clear();
for( public_key_type& key : approving_key_set )
{
auto it = _keys.find(key);
if( it != _keys.end() )
{
fc::optional<fc::ecc::private_key> privkey = wif_to_key( it->second );
FC_ASSERT( privkey.valid(), "Malformed private key in _keys" );
tx.sign( *privkey );
}
/// TODO: if transaction has enough signatures to be "valid" don't add any more,
/// there are cases where the wallet may have more keys than strictly necessary and
/// the transaction will be rejected if the transaction validates without requiring
/// all signatures provided
}
graphene::chain::transaction_id_type this_transaction_id = tx.id();
auto iter = _recently_generated_transactions.find(this_transaction_id);
if (iter == _recently_generated_transactions.end())
{
// we haven't generated this transaction before, the usual case
recently_generated_transaction_record this_transaction_record;
this_transaction_record.generation_time = dyn_props.time;
this_transaction_record.transaction_id = this_transaction_id;
_recently_generated_transactions.insert(this_transaction_record);
break;
}
// else we've generated a dupe, increment expiration time and re-sign it
++expiration_time_offset;
}
if( broadcast )
{
try
{
_remote_net_broadcast->broadcast_transaction( tx );
}
catch (const fc::exception& e)
{
elog("Caught exception while broadcasting transaction with id ${id}", ("id", tx.id().str()));
throw;
}
}
return tx;
}
signed_transaction sell_asset(string seller_account,
string amount_to_sell,
string symbol_to_sell,
string min_to_receive,
string symbol_to_receive,
uint32_t timeout_sec = 0,
bool fill_or_kill = false,
bool broadcast = false)
{
account_object seller = get_account( seller_account );
limit_order_create_operation op;
op.seller = seller.id;
op.amount_to_sell = get_asset(symbol_to_sell).amount_from_string(amount_to_sell);
op.min_to_receive = get_asset(symbol_to_receive).amount_from_string(min_to_receive);
if( timeout_sec )
op.expiration = fc::time_point::now() + fc::seconds(timeout_sec);
op.fill_or_kill = fill_or_kill;
signed_transaction tx;
tx.operations.push_back(op);
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction( tx, broadcast );
}
signed_transaction borrow_asset(string seller_name, string amount_to_borrow, string asset_symbol,
string amount_of_collateral, bool broadcast = false)
{
account_object seller = get_account(seller_name);
asset_object mia = get_asset(asset_symbol);
FC_ASSERT(mia.is_market_issued());
asset_object collateral = get_asset(get_object(*mia.bitasset_data_id).options.short_backing_asset);
call_order_update_operation op;
op.funding_account = seller.id;
op.delta_debt = mia.amount_from_string(amount_to_borrow);
op.delta_collateral = collateral.amount_from_string(amount_of_collateral);
signed_transaction trx;
trx.operations = {op};
set_operation_fees( trx, _remote_db->get_global_properties().parameters.current_fees);
trx.validate();
idump((broadcast));
return sign_transaction(trx, broadcast);
}
signed_transaction cancel_order(object_id_type order_id, bool broadcast = false)
{ try {
FC_ASSERT(!is_locked());
FC_ASSERT(order_id.space() == protocol_ids, "Invalid order ID ${id}", ("id", order_id));
signed_transaction trx;
limit_order_cancel_operation op;
op.fee_paying_account = get_object<limit_order_object>(order_id).seller;
op.order = order_id;
set_operation_fees( trx, _remote_db->get_global_properties().parameters.current_fees);
trx.operations = {op};
trx.validate();
return sign_transaction(trx, broadcast);
} FC_CAPTURE_AND_RETHROW((order_id)) }
signed_transaction transfer(string from, string to, string amount,
string asset_symbol, string memo, bool broadcast = false)
{ try {
FC_ASSERT( !self.is_locked() );
fc::optional<asset_object> asset_obj = get_asset(asset_symbol);
FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol));
account_object from_account = get_account(from);
account_object to_account = get_account(to);
account_id_type from_id = from_account.id;
account_id_type to_id = get_account_id(to);
transfer_operation xfer_op;
xfer_op.from = from_id;
xfer_op.to = to_id;
xfer_op.amount = asset_obj->amount_from_string(amount);
if( memo.size() )
{
xfer_op.memo = memo_data();
xfer_op.memo->from = from_account.options.memo_key;
xfer_op.memo->to = to_account.options.memo_key;
xfer_op.memo->set_message(get_private_key(from_account.options.memo_key),
to_account.options.memo_key, memo);
}
signed_transaction tx;
tx.operations.push_back(xfer_op);
set_operation_fees( tx, _remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction(tx, broadcast);
} FC_CAPTURE_AND_RETHROW( (from)(to)(amount)(asset_symbol)(memo)(broadcast) ) }
signed_transaction issue_asset(string to_account, string amount, string symbol,
string memo, bool broadcast = false)
{
auto asset_obj = get_asset(symbol);
account_object to = get_account(to_account);
account_object issuer = get_account(asset_obj.issuer);
asset_issue_operation issue_op;
issue_op.issuer = asset_obj.issuer;
issue_op.asset_to_issue = asset_obj.amount_from_string(amount);
issue_op.issue_to_account = to.id;
if( memo.size() )
{
issue_op.memo = memo_data();
issue_op.memo->from = issuer.options.memo_key;
issue_op.memo->to = to.options.memo_key;
issue_op.memo->set_message(get_private_key(issuer.options.memo_key),
to.options.memo_key, memo);
}
signed_transaction tx;
tx.operations.push_back(issue_op);
set_operation_fees(tx,_remote_db->get_global_properties().parameters.current_fees);
tx.validate();
return sign_transaction(tx, broadcast);
}
std::map<string,std::function<string(fc::variant,const fc::variants&)>> get_result_formatters() const
{
std::map<string,std::function<string(fc::variant,const fc::variants&)> > m;
m["help"] = [](variant result, const fc::variants& a)
{
return result.get_string();
};
m["gethelp"] = [](variant result, const fc::variants& a)
{
return result.get_string();
};
m["get_account_history"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<vector<operation_history_object>>();
std::stringstream ss;
for( const operation_history_object& i : r )
{
auto b = _remote_db->get_block_header(i.block_num);
FC_ASSERT(b);
ss << b->timestamp.to_iso_string() << " ";
i.op.visit(operation_printer(ss, *this, i.result));
ss << " \n";
}
return ss.str();
};
m["list_account_balances"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<vector<asset>>();
vector<asset_object> asset_recs;
std::transform(r.begin(), r.end(), std::back_inserter(asset_recs), [this](const asset& a) {
return get_asset(a.asset_id);
});
std::stringstream ss;
for( unsigned i = 0; i < asset_recs.size(); ++i )
ss << asset_recs[i].amount_to_pretty_string(r[i]) << "\n";
return ss.str();
};
m["get_blind_balances"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<vector<asset>>();
vector<asset_object> asset_recs;
std::transform(r.begin(), r.end(), std::back_inserter(asset_recs), [this](const asset& a) {
return get_asset(a.asset_id);
});
std::stringstream ss;
for( unsigned i = 0; i < asset_recs.size(); ++i )
ss << asset_recs[i].amount_to_pretty_string(r[i]) << "\n";
return ss.str();
};
m["transfer_to_blind"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<blind_confirmation>();
std::stringstream ss;
r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) );
ss << "\n";
for( const auto& out : r.outputs )
{
asset_object a = get_asset( out.decrypted_memo.amount.asset_id );
ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n";
}
return ss.str();
};
m["blind_transfer"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<blind_confirmation>();
std::stringstream ss;
r.trx.operations[0].visit( operation_printer( ss, *this, operation_result() ) );
ss << "\n";
for( const auto& out : r.outputs )
{
asset_object a = get_asset( out.decrypted_memo.amount.asset_id );
ss << a.amount_to_pretty_string( out.decrypted_memo.amount ) << " to " << out.label << "\n\t receipt: " << out.confirmation_receipt <<"\n\n";
}
return ss.str();
};
m["receive_blind_transfer"] = [this](variant result, const fc::variants& a)
{
auto r = result.as<blind_receipt>();
std::stringstream ss;
asset_object as = get_asset( r.amount.asset_id );
ss << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n";
return ss.str();
};
m["blind_history"] = [this](variant result, const fc::variants& a)
{
auto records = result.as<vector<blind_receipt>>();
std::stringstream ss;
ss << "WHEN "
<< " " << "AMOUNT" << " " << "FROM" << " => " << "TO" << " " << "MEMO" <<"\n";
ss << "====================================================================================\n";
for( auto& r : records )
{
asset_object as = get_asset( r.amount.asset_id );
ss << fc::get_approximate_relative_time_string( r.date )
<< " " << as.amount_to_pretty_string( r.amount ) << " " << r.from_label << " => " << r.to_label << " " << r.memo <<"\n";
}
return ss.str();
};
return m;
}
void dbg_make_uia(string creator, string symbol)
{
asset_options opts;
opts.flags &= ~(white_list | disable_force_settle | global_settle);
opts.issuer_permissions = opts.flags;
opts.core_exchange_rate = price(asset(1), asset(1,1));
create_asset(get_account(creator).name, symbol, 2, opts, {}, true);
}
void dbg_make_mia(string creator, string symbol)
{
asset_options opts;
opts.flags &= ~white_list;
opts.issuer_permissions = opts.flags;
opts.core_exchange_rate = price(asset(1), asset(1,1));
bitasset_options bopts;
create_asset(get_account(creator).name, symbol, 2, opts, bopts, true);
}
void flood_network(string prefix, uint32_t number_of_transactions)
{
try
{
const account_object& master = *_wallet.my_accounts.get<by_name>().lower_bound("import");
int number_of_accounts = number_of_transactions / 3;
number_of_transactions -= number_of_accounts;
//auto key = derive_private_key("floodshill", 0);
try {
dbg_make_uia(master.name, "SHILL");
} catch(...) {/* Ignore; the asset probably already exists.*/}
fc::time_point start = fc::time_point::now();
for( int i = 0; i < number_of_accounts; ++i )
{
std::ostringstream brain_key;
brain_key << "brain key for account " << prefix << i;
signed_transaction trx = create_account_with_brain_key(brain_key.str(), prefix + fc::to_string(i), master.name, master.name, /* broadcast = */ true, /* save wallet = */ false);
}
fc::time_point end = fc::time_point::now();
ilog("Created ${n} accounts in ${time} milliseconds",
("n", number_of_accounts)("time", (end - start).count() / 1000));
start = fc::time_point::now();
for( int i = 0; i < number_of_accounts; ++i )
{
signed_transaction trx = transfer(master.name, prefix + fc::to_string(i), "10", "CORE", "", true);
trx = transfer(master.name, prefix + fc::to_string(i), "1", "CORE", "", true);
}
end = fc::time_point::now();
ilog("Transferred to ${n} accounts in ${time} milliseconds",
("n", number_of_accounts*2)("time", (end - start).count() / 1000));
start = fc::time_point::now();
for( int i = 0; i < number_of_accounts; ++i )
{
signed_transaction trx = issue_asset(prefix + fc::to_string(i), "1000", "SHILL", "", true);
}
end = fc::time_point::now();
ilog("Issued to ${n} accounts in ${time} milliseconds",
("n", number_of_accounts)("time", (end - start).count() / 1000));
}
catch (...)
{
throw;
}
}
operation get_prototype_operation( string operation_name )
{
auto it = _prototype_ops.find( operation_name );
if( it == _prototype_ops.end() )
FC_THROW("Unsupported operation: \"${operation_name}\"", ("operation_name", operation_name));
return it->second;
}
string _wallet_filename;
wallet_data _wallet;
map<public_key_type,string> _keys;
fc::sha512 _checksum;
fc::api<login_api> _remote_api;
fc::api<database_api> _remote_db;
fc::api<network_broadcast_api> _remote_net_broadcast;
fc::api<history_api> _remote_hist;
flat_map<string, operation> _prototype_ops;
#ifdef __unix__
mode_t _old_umask;
#endif
const string _wallet_filename_extension = ".wallet";
mutable map<asset_id_type, asset_object> _asset_cache;
};
void operation_printer::fee(const asset& a)const {
out << " (Fee: " << wallet.get_asset(a.asset_id).amount_to_pretty_string(a) << ")";
}
template<typename T>
void operation_printer::operator()(const T& op)const
{
//balance_accumulator acc;
//op.get_balance_delta( acc, result );
auto a = wallet.get_asset( op.fee.asset_id );
auto payer = wallet.get_account( op.fee_payer() );
string op_name = fc::get_typename<T>::name();
if( op_name.find_last_of(':') != string::npos )
op_name.erase(0, op_name.find_last_of(':')+1);
out << op_name <<" ";
// out << "balance delta: " << fc::json::to_string(acc.balance) <<" ";
out << payer.name << " fee: " << a.amount_to_pretty_string( op.fee );
}
void operation_printer::operator()(const transfer_from_blind_operation& op)const
{
auto a = wallet.get_asset( op.fee.asset_id );
auto receiver = wallet.get_account( op.to );
out << receiver.name
<< " received " << a.amount_to_pretty_string( op.amount ) << " from blinded balance";
}
void operation_printer::operator()(const transfer_to_blind_operation& op)const
{
auto fa = wallet.get_asset( op.fee.asset_id );
auto a = wallet.get_asset( op.amount.asset_id );
auto sender = wallet.get_account( op.from );
out << sender.name
<< " sent " << a.amount_to_pretty_string( op.amount ) << " to " << op.outputs.size() << " blinded balance" << (op.outputs.size()>1?"s":"")
<< " fee: " << fa.amount_to_pretty_string( op.fee );
}
void operation_printer::operator()(const transfer_operation& op) const
{
out << "Transfer " << wallet.get_asset(op.amount.asset_id).amount_to_pretty_string(op.amount)
<< " from " << wallet.get_account(op.from).name << " to " << wallet.get_account(op.to).name;
if( op.memo )
{
if( wallet.is_locked() )
{
out << " -- Unlock wallet to see memo.";
} else {
try {
FC_ASSERT(wallet._keys.count(op.memo->to), "Memo is encrypted to a key ${k} not in this wallet.",
("k", op.memo->to));
auto my_key = wif_to_key(wallet._keys.at(op.memo->to));
FC_ASSERT(my_key, "Unable to recover private key to decrypt memo. Wallet may be corrupted.");
out << " -- Memo: " << op.memo->get_message(*my_key, op.memo->from);
} catch (const fc::exception& e) {
out << " -- could not decrypt memo";
elog("Error when decrypting memo: ${e}", ("e", e.to_detail_string()));
}
}
}
fee(op.fee);
}
void operation_printer::operator()(const account_create_operation& op) const
{
out << "Create Account '" << op.name << "'";
fee(op.fee);
}
void operation_printer::operator()(const account_update_operation& op) const
{
out << "Update Account '" << wallet.get_account(op.account).name << "'";
fee(op.fee);
}
void operation_printer::operator()(const asset_create_operation& op) const
{
out << "Create ";
if( op.bitasset_opts.valid() )
out << "BitAsset ";
else
out << "User-Issue Asset ";
out << "'" << op.symbol << "' with issuer " << wallet.get_account(op.issuer).name;
fee(op.fee);
}
}}}
namespace graphene { namespace wallet {
wallet_api::wallet_api(fc::api<login_api> rapi)
: my(new detail::wallet_api_impl(*this, rapi))
{
}
wallet_api::~wallet_api()
{
}
bool wallet_api::copy_wallet_file(string destination_filename)
{
return my->copy_wallet_file(destination_filename);
}
optional<signed_block> wallet_api::get_block(uint32_t num)
{
return my->_remote_db->get_block(num);
}
uint64_t wallet_api::get_account_count() const
{
return my->_remote_db->get_account_count();
}
vector<account_object> wallet_api::list_my_accounts()
{
return vector<account_object>(my->_wallet.my_accounts.begin(), my->_wallet.my_accounts.end());
}
map<string,account_id_type> wallet_api::list_accounts(const string& lowerbound, uint32_t limit)
{
return my->_remote_db->lookup_accounts(lowerbound, limit);
}
vector<asset> wallet_api::list_account_balances(const string& id)
{
if( auto real_id = detail::maybe_id<account_id_type>(id) )
return my->_remote_db->get_account_balances(*real_id, flat_set<asset_id_type>());
return my->_remote_db->get_account_balances(get_account(id).id, flat_set<asset_id_type>());
}
vector<asset_object> wallet_api::list_assets(const string& lowerbound, uint32_t limit)const
{
return my->_remote_db->list_assets( lowerbound, limit );
}
vector<operation_history_object> wallet_api::get_account_history(string name, int limit)const
{
return my->_remote_hist->get_account_history(get_account(name).get_id(), operation_history_id_type(), limit, operation_history_id_type());
}
vector<bucket_object> wallet_api::get_market_history( string symbol1, string symbol2, uint32_t bucket )const
{
return my->_remote_hist->get_market_history( get_asset_id(symbol1), get_asset_id(symbol2), bucket, fc::time_point_sec(), fc::time_point::now() );
}
vector<limit_order_object> wallet_api::get_limit_orders(string a, string b, uint32_t limit)const
{
return my->_remote_db->get_limit_orders(get_asset(a).id, get_asset(b).id, limit);
}
vector<call_order_object> wallet_api::get_call_orders(string a, uint32_t limit)const
{
return my->_remote_db->get_call_orders(get_asset(a).id, limit);
}
vector<force_settlement_object> wallet_api::get_settle_orders(string a, uint32_t limit)const
{
return my->_remote_db->get_settle_orders(get_asset(a).id, limit);
}
brain_key_info wallet_api::suggest_brain_key()const
{
brain_key_info result;
// create a private key for secure entropy
fc::sha256 sha_entropy1 = fc::ecc::private_key::generate().get_secret();
fc::sha256 sha_entropy2 = fc::ecc::private_key::generate().get_secret();
fc::bigint entropy1( sha_entropy1.data(), sha_entropy1.data_size() );
fc::bigint entropy2( sha_entropy2.data(), sha_entropy2.data_size() );
fc::bigint entropy(entropy1);
entropy <<= 8*sha_entropy1.data_size();
entropy += entropy2;
string brain_key = "";
for( int i=0; i<BRAIN_KEY_WORD_COUNT; i++ )
{
fc::bigint choice = entropy % graphene::words::word_list_size;
entropy /= graphene::words::word_list_size;
if( i > 0 )
brain_key += " ";
brain_key += graphene::words::word_list[ choice.to_int64() ];
}
brain_key = normalize_brain_key(brain_key);
fc::ecc::private_key priv_key = derive_private_key( brain_key, 0 );
result.brain_priv_key = brain_key;
result.wif_priv_key = key_to_wif( priv_key );
result.pub_key = priv_key.get_public_key();
return result;
}
string wallet_api::serialize_transaction( signed_transaction tx )const
{
return fc::to_hex(fc::raw::pack(tx));
}
variant wallet_api::get_object( object_id_type id ) const
{
return my->_remote_db->get_objects({id});
}
string wallet_api::get_wallet_filename() const
{
return my->get_wallet_filename();
}
transaction_handle_type wallet_api::begin_builder_transaction()
{
return my->begin_builder_transaction();
}
void wallet_api::add_operation_to_builder_transaction(transaction_handle_type transaction_handle, const operation& op)
{
my->add_operation_to_builder_transaction(transaction_handle, op);
}
void wallet_api::replace_operation_in_builder_transaction(transaction_handle_type handle, unsigned operation_index, const operation& new_op)
{
my->replace_operation_in_builder_transaction(handle, operation_index, new_op);
}
asset wallet_api::set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset)
{
return my->set_fees_on_builder_transaction(handle, fee_asset);
}
transaction wallet_api::preview_builder_transaction(transaction_handle_type handle)
{
return my->preview_builder_transaction(handle);
}
signed_transaction wallet_api::sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast)
{
return my->sign_builder_transaction(transaction_handle, broadcast);
}
signed_transaction wallet_api::propose_builder_transaction(transaction_handle_type handle, time_point_sec expiration, uint32_t review_period_seconds, bool broadcast)
{
return my->propose_builder_transaction(handle, expiration, review_period_seconds, broadcast);
}
void wallet_api::remove_builder_transaction(transaction_handle_type handle)
{
return my->remove_builder_transaction(handle);
}
account_object wallet_api::get_account(string account_name_or_id) const
{
return my->get_account(account_name_or_id);
}
asset_object wallet_api::get_asset(string asset_name_or_id) const
{
auto a = my->find_asset(asset_name_or_id);
FC_ASSERT(a);
return *a;
}
asset_bitasset_data_object wallet_api::get_bitasset_data(string asset_name_or_id) const
{
auto asset = get_asset(asset_name_or_id);
FC_ASSERT(asset.is_market_issued() && asset.bitasset_data_id);
return my->get_object<asset_bitasset_data_object>(*asset.bitasset_data_id);
}
account_id_type wallet_api::get_account_id(string account_name_or_id) const
{
return my->get_account_id(account_name_or_id);
}
asset_id_type wallet_api::get_asset_id(string asset_symbol_or_id) const
{
return my->get_asset_id(asset_symbol_or_id);
}
bool wallet_api::import_key(string account_name_or_id, string wif_key)
{
FC_ASSERT(!is_locked());
// backup wallet
fc::optional<fc::ecc::private_key> optional_private_key = wif_to_key(wif_key);
if (!optional_private_key)
FC_THROW("Invalid private key ${key}", ("key", wif_key));
string shorthash = detail::address_to_shorthash(optional_private_key->get_public_key());
copy_wallet_file( "before-import-key-" + shorthash );
if( my->import_key(account_name_or_id, wif_key) )
{
save_wallet_file();
copy_wallet_file( "after-import-key-" + shorthash );
return true;
}
return false;
}
string wallet_api::normalize_brain_key(string s) const
{
return detail::normalize_brain_key( s );
}
variant wallet_api::info()
{
return my->info();
}
fc::ecc::private_key wallet_api::derive_private_key(const std::string& prefix_string, int sequence_number) const
{
return detail::derive_private_key( prefix_string, sequence_number );
}
signed_transaction wallet_api::register_account(string name,
public_key_type owner_pubkey,
public_key_type active_pubkey,
string registrar_account,
string referrer_account,
uint8_t referrer_percent,
bool broadcast)
{
return my->register_account( name, owner_pubkey, active_pubkey, registrar_account, referrer_account, referrer_percent, broadcast );
}
signed_transaction wallet_api::create_account_with_brain_key(string brain_key, string account_name,
string registrar_account, string referrer_account,
bool broadcast /* = false */)
{
return my->create_account_with_brain_key(
brain_key, account_name, registrar_account,
referrer_account, broadcast
);
}
signed_transaction wallet_api::issue_asset(string to_account, string amount, string symbol,
string memo, bool broadcast)
{
return my->issue_asset(to_account, amount, symbol, memo, broadcast);
}
signed_transaction wallet_api::transfer(string from, string to, string amount,
string asset_symbol, string memo, bool broadcast /* = false */)
{
return my->transfer(from, to, amount, asset_symbol, memo, broadcast);
}
signed_transaction wallet_api::create_asset(string issuer,
string symbol,
uint8_t precision,
asset_options common,
fc::optional<bitasset_options> bitasset_opts,
bool broadcast)
{
return my->create_asset(issuer, symbol, precision, common, bitasset_opts, broadcast);
}
signed_transaction wallet_api::update_asset(string symbol,
optional<string> new_issuer,
asset_options new_options,
bool broadcast /* = false */)
{
return my->update_asset(symbol, new_issuer, new_options, broadcast);
}
signed_transaction wallet_api::update_bitasset(string symbol,
bitasset_options new_options,
bool broadcast /* = false */)
{
return my->update_bitasset(symbol, new_options, broadcast);
}
signed_transaction wallet_api::update_asset_feed_producers(string symbol,
flat_set<string> new_feed_producers,
bool broadcast /* = false */)
{
return my->update_asset_feed_producers(symbol, new_feed_producers, broadcast);
}
signed_transaction wallet_api::publish_asset_feed(string publishing_account,
string symbol,
price_feed feed,
bool broadcast /* = false */)
{
return my->publish_asset_feed(publishing_account, symbol, feed, broadcast);
}
signed_transaction wallet_api::fund_asset_fee_pool(string from,
string symbol,
string amount,
bool broadcast /* = false */)
{
return my->fund_asset_fee_pool(from, symbol, amount, broadcast);
}
signed_transaction wallet_api::reserve_asset(string from,
string amount,
string symbol,
bool broadcast /* = false */)
{
return my->fund_asset_fee_pool(from, amount, symbol, broadcast);
}
signed_transaction wallet_api::global_settle_asset(string symbol,
price settle_price,
bool broadcast /* = false */)
{
return my->global_settle_asset(symbol, settle_price, broadcast);
}
signed_transaction wallet_api::settle_asset(string account_to_settle,
string amount_to_settle,
string symbol,
bool broadcast /* = false */)
{
return my->settle_asset(account_to_settle, amount_to_settle, symbol, broadcast);
}
signed_transaction wallet_api::whitelist_account(string authorizing_account,
string account_to_list,
account_whitelist_operation::account_listing new_listing_status,
bool broadcast /* = false */)
{
return my->whitelist_account(authorizing_account, account_to_list, new_listing_status, broadcast);
}
signed_transaction wallet_api::create_committee_member(string owner_account, string url,
bool broadcast /* = false */)
{
return my->create_committee_member(owner_account, url, broadcast);
}
map<string,witness_id_type> wallet_api::list_witnesses(const string& lowerbound, uint32_t limit)
{
return my->_remote_db->lookup_witness_accounts(lowerbound, limit);
}
map<string,committee_member_id_type> wallet_api::list_committee_members(const string& lowerbound, uint32_t limit)
{
return my->_remote_db->lookup_committee_member_accounts(lowerbound, limit);
}
witness_object wallet_api::get_witness(string owner_account)
{
return my->get_witness(owner_account);
}
committee_member_object wallet_api::get_committee_member(string owner_account)
{
return my->get_committee_member(owner_account);
}
signed_transaction wallet_api::create_witness(string owner_account,
string url,
bool broadcast /* = false */)
{
return my->create_witness(owner_account, url, broadcast);
}
signed_transaction wallet_api::vote_for_committee_member(string voting_account,
string witness,
bool approve,
bool broadcast /* = false */)
{
return my->vote_for_committee_member(voting_account, witness, approve, broadcast);
}
signed_transaction wallet_api::vote_for_witness(string voting_account,
string witness,
bool approve,
bool broadcast /* = false */)
{
return my->vote_for_witness(voting_account, witness, approve, broadcast);
}
signed_transaction wallet_api::set_voting_proxy(string account_to_modify,
optional<string> voting_account,
bool broadcast /* = false */)
{
return my->set_voting_proxy(account_to_modify, voting_account, broadcast);
}
signed_transaction wallet_api::set_desired_witness_and_committee_member_count(string account_to_modify,
uint16_t desired_number_of_witnesses,
uint16_t desired_number_of_committee_members,
bool broadcast /* = false */)
{
return my->set_desired_witness_and_committee_member_count(account_to_modify, desired_number_of_witnesses,
desired_number_of_committee_members, broadcast);
}
void wallet_api::set_wallet_filename(string wallet_filename)
{
my->_wallet_filename = wallet_filename;
}
signed_transaction wallet_api::sign_transaction(signed_transaction tx, bool broadcast /* = false */)
{ try {
return my->sign_transaction( tx, broadcast);
} FC_CAPTURE_AND_RETHROW( (tx) ) }
operation wallet_api::get_prototype_operation(string operation_name)
{
return my->get_prototype_operation( operation_name );
}
void wallet_api::dbg_make_uia(string creator, string symbol)
{
FC_ASSERT(!is_locked());
my->dbg_make_uia(creator, symbol);
}
void wallet_api::dbg_make_mia(string creator, string symbol)
{
FC_ASSERT(!is_locked());
my->dbg_make_mia(creator, symbol);
}
void wallet_api::flood_network(string prefix, uint32_t number_of_transactions)
{
FC_ASSERT(!is_locked());
my->flood_network(prefix, number_of_transactions);
}
global_property_object wallet_api::get_global_properties() const
{
return my->get_global_properties();
}
dynamic_global_property_object wallet_api::get_dynamic_global_properties() const
{
return my->get_dynamic_global_properties();
}
string wallet_api::help()const
{
std::vector<std::string> method_names = my->method_documentation.get_method_names();
std::stringstream ss;
for (const std::string method_name : method_names)
{
try
{
ss << my->method_documentation.get_brief_description(method_name);
}
catch (const fc::key_not_found_exception&)
{
ss << method_name << " (no help available)\n";
}
}
return ss.str();
}
string wallet_api::gethelp(const string& method)const
{
fc::api<wallet_api> tmp;
std::stringstream ss;
ss << "\n";
if( method == "import_key" )
{
ss << "usage: import_key ACCOUNT_NAME_OR_ID WIF_PRIVATE_KEY\n\n";
ss << "example: import_key \"1.3.11\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\n";
ss << "example: import_key \"usera\" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\n";
}
else if( method == "transfer" )
{
ss << "usage: transfer FROM TO AMOUNT SYMBOL \"memo\" BROADCAST\n\n";
ss << "example: transfer \"1.3.11\" \"1.3.4\" 1000.03 CORE \"memo\" true\n";
ss << "example: transfer \"usera\" \"userb\" 1000.123 CORE \"memo\" true\n";
}
else if( method == "create_account_with_brain_key" )
{
ss << "usage: create_account_with_brain_key BRAIN_KEY ACCOUNT_NAME REGISTRAR REFERRER BROADCAST\n\n";
ss << "example: create_account_with_brain_key \"my really long brain key\" \"newaccount\" \"1.3.11\" \"1.3.11\" true\n";
ss << "example: create_account_with_brain_key \"my really long brain key\" \"newaccount\" \"someaccount\" \"otheraccount\" true\n";
ss << "\n";
ss << "This method should be used if you would like the wallet to generate new keys derived from the brain key.\n";
ss << "The BRAIN_KEY will be used as the owner key, and the active key will be derived from the BRAIN_KEY. Use\n";
ss << "register_account if you already know the keys you know the public keys that you would like to register.\n";
}
else if( method == "register_account" )
{
ss << "usage: register_account ACCOUNT_NAME OWNER_PUBLIC_KEY ACTIVE_PUBLIC_KEY REGISTRAR REFERRER REFERRER_PERCENT BROADCAST\n\n";
ss << "example: register_account \"newaccount\" \"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\" \"CORE6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV\" \"1.3.11\" \"1.3.11\" 50 true\n";
ss << "\n";
ss << "Use this method to register an account for which you do not know the private keys.";
}
else if( method == "create_asset" )
{
ss << "usage: ISSUER SYMBOL PRECISION_DIGITS OPTIONS BITASSET_OPTIONS BROADCAST\n\n";
ss << "PRECISION_DIGITS: the number of digits after the decimal point\n\n";
ss << "Example value of OPTIONS: \n";
ss << fc::json::to_pretty_string( graphene::chain::asset_options() );
ss << "\nExample value of BITASSET_OPTIONS: \n";
ss << fc::json::to_pretty_string( graphene::chain::bitasset_options() );
ss << "\nBITASSET_OPTIONS may be null\n";
}
else
{
std::string doxygenHelpString = my->method_documentation.get_detailed_description(method);
if (!doxygenHelpString.empty())
ss << doxygenHelpString;
else
ss << "No help defined for method " << method << "\n";
}
return ss.str();
}
bool wallet_api::load_wallet_file( string wallet_filename )
{
return my->load_wallet_file( wallet_filename );
}
void wallet_api::save_wallet_file( string wallet_filename )
{
my->save_wallet_file( wallet_filename );
}
std::map<string,std::function<string(fc::variant,const fc::variants&)> >
wallet_api::get_result_formatters() const
{
return my->get_result_formatters();
}
bool wallet_api::is_locked()const
{
return my->is_locked();
}
bool wallet_api::is_new()const
{
return my->_wallet.cipher_keys.size() == 0;
}
void wallet_api::encrypt_keys()
{
my->encrypt_keys();
}
void wallet_api::lock()
{ try {
FC_ASSERT( !is_locked() );
encrypt_keys();
for( auto key : my->_keys )
key.second = key_to_wif(fc::ecc::private_key());
my->_keys.clear();
my->_checksum = fc::sha512();
my->self.lock_changed(true);
} FC_CAPTURE_AND_RETHROW() }
void wallet_api::unlock(string password)
{ try {
FC_ASSERT(password.size() > 0);
auto pw = fc::sha512::hash(password.c_str(), password.size());
vector<char> decrypted = fc::aes_decrypt(pw, my->_wallet.cipher_keys);
auto pk = fc::raw::unpack<plain_keys>(decrypted);
FC_ASSERT(pk.checksum == pw);
my->_keys = std::move(pk.keys);
my->_checksum = pk.checksum;
my->self.lock_changed(false);
} FC_CAPTURE_AND_RETHROW() }
void wallet_api::set_password( string password )
{
if( !is_new() )
FC_ASSERT( !is_locked(), "The wallet must be unlocked before the password can be set" );
my->_checksum = fc::sha512::hash( password.c_str(), password.size() );
lock();
}
signed_transaction wallet_api::import_balance( string name_or_id, const vector<string>& wif_keys, bool broadcast )
{ try {
FC_ASSERT(!is_locked());
account_object claimer = get_account( name_or_id );
vector<address> addrs;
map<address,private_key_type> keys;
for( auto wif_key : wif_keys )
{
auto priv_key = wif_to_key(wif_key);
FC_ASSERT( priv_key, "Invalid Private Key", ("key",wif_key) );
addrs.push_back( priv_key->get_public_key() );
keys[addrs.back()] = *priv_key;
}
auto balances = my->_remote_db->get_balance_objects( addrs );
wdump((balances));
addrs.clear();
set<asset_id_type> bal_types;
for( auto b : balances ) bal_types.insert( b.balance.asset_id );
set<address> required_addrs;
signed_transaction trx;
for( auto a : bal_types )
{
balance_claim_operation op;
op.deposit_to_account = claimer.id;
for( const auto& b : balances )
{
if( b.balance.asset_id == a )
{
op.total_claimed = b.vesting_policy.valid()? asset(0, b.balance.asset_id) : b.balance;
op.balance_to_claim = b.id;
op.balance_owner_key = keys[b.owner].get_public_key();
trx.operations.push_back(op);
required_addrs.insert(b.owner);
}
}
}
my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees );
trx.validate();
auto tx = sign_transaction( trx, false );
for( auto a : required_addrs )
tx.sign( keys[a] );
// if the key for a balance object was the same as a key for the account we're importing it into,
// we may end up with duplicate signatures, so remove those
boost::erase(tx.signatures, boost::unique<boost::return_found_end>(boost::sort(tx.signatures)));
if( broadcast )
my->_remote_net_broadcast->broadcast_transaction(tx);
return tx;
} FC_CAPTURE_AND_RETHROW( (name_or_id) ) }
map<public_key_type, string> wallet_api::dump_private_keys()
{
FC_ASSERT(!is_locked());
return my->_keys;
}
signed_transaction wallet_api::upgrade_account( string name, bool broadcast )
{
return my->upgrade_account(name,broadcast);
}
signed_transaction wallet_api::sell_asset(string seller_account,
string amount_to_sell,
string symbol_to_sell,
string min_to_receive,
string symbol_to_receive,
uint32_t expiration,
bool fill_or_kill,
bool broadcast)
{
return my->sell_asset(seller_account, amount_to_sell, symbol_to_sell, min_to_receive,
symbol_to_receive, expiration, fill_or_kill, broadcast);
}
signed_transaction wallet_api::borrow_asset(string seller_name, string amount_to_sell,
string asset_symbol, string amount_of_collateral, bool broadcast)
{
FC_ASSERT(!is_locked());
return my->borrow_asset(seller_name, amount_to_sell, asset_symbol, amount_of_collateral, broadcast);
}
string wallet_api::get_key_label( public_key_type key )const
{
auto key_itr = my->_wallet.labeled_keys.get<by_key>().find(key);
if( key_itr != my->_wallet.labeled_keys.get<by_key>().end() )
return key_itr->label;
return string();
}
public_key_type wallet_api::get_public_key( string label )const
{
try { return fc::variant(label).as<public_key_type>(); } catch ( ... ){}
auto key_itr = my->_wallet.labeled_keys.get<by_label>().find(label);
if( key_itr != my->_wallet.labeled_keys.get<by_label>().end() )
return key_itr->key;
return public_key_type();
}
bool wallet_api::set_key_label( public_key_type key, string label )
{
auto result = my->_wallet.labeled_keys.insert( key_label{label,key} );
if( result.second ) return true;
auto key_itr = my->_wallet.labeled_keys.get<by_key>().find(key);
auto label_itr = my->_wallet.labeled_keys.get<by_label>().find(label);
if( label_itr == my->_wallet.labeled_keys.get<by_label>().end() )
{
if( key_itr != my->_wallet.labeled_keys.get<by_key>().end() )
return my->_wallet.labeled_keys.get<by_key>().modify( key_itr, [&]( key_label& obj ){ obj.label = label; } );
}
return false;
}
map<string,public_key_type> wallet_api::get_blind_accounts()const
{
map<string,public_key_type> result;
for( const auto& item : my->_wallet.labeled_keys )
result[item.label] = item.key;
return result;
}
map<string,public_key_type> wallet_api::get_my_blind_accounts()const
{
FC_ASSERT( !is_locked() );
map<string,public_key_type> result;
for( const auto& item : my->_wallet.labeled_keys )
{
if( my->_keys.find(item.key) != my->_keys.end() )
result[item.label] = item.key;
}
return result;
}
public_key_type wallet_api::create_blind_account( string label, string brain_key )
{
FC_ASSERT( !is_locked() );
auto label_itr = my->_wallet.labeled_keys.get<by_label>().find(label);
if( label_itr != my->_wallet.labeled_keys.get<by_label>().end() )
FC_ASSERT( !"Key with label already exists" );
brain_key = fc::trim_and_normalize_spaces( brain_key );
auto secret = fc::sha256::hash( brain_key.c_str(), brain_key.size() );
auto priv_key = fc::ecc::private_key::regenerate( secret );
public_key_type pub_key = priv_key.get_public_key();
FC_ASSERT( set_key_label( pub_key, label ) );
my->_keys[pub_key] = graphene::utilities::key_to_wif( priv_key );
save_wallet_file();
return pub_key;
}
vector<asset> wallet_api::get_blind_balances( string key_or_label )
{
vector<asset> result;
map<asset_id_type, share_type> balances;
vector<commitment_type> used;
auto pub_key = get_public_key( key_or_label );
auto& to_asset_used_idx = my->_wallet.blind_receipts.get<by_to_asset_used>();
auto start = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,0,false) );
auto end = to_asset_used_idx.lower_bound( std::make_tuple(pub_key,uint32_t(0xffffffff),true) );
while( start != end )
{
if( !start->used )
{
auto answer = my->_remote_db->get_blinded_balances( {start->commitment()} );
if( answer.size() )
balances[start->amount.asset_id] += start->amount.amount;
else
used.push_back( start->commitment() );
}
++start;
}
for( const auto& u : used )
{
auto itr = my->_wallet.blind_receipts.get<by_commitment>().find( u );
my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } );
}
for( auto item : balances )
result.push_back( asset( item.second, item.first ) );
return result;
}
blind_confirmation wallet_api::transfer_from_blind( string from_blind_account_key_or_label,
string to_account_id_or_name,
string amount_in,
string symbol,
bool broadcast )
{ try {
transfer_from_blind_operation from_blind;
auto fees = my->_remote_db->get_global_properties().parameters.current_fees;
fc::optional<asset_object> asset_obj = get_asset(symbol);
FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol));
auto amount = asset_obj->amount_from_string(amount_in);
from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate );
auto blind_in = asset_obj->amount_to_string( from_blind.fee + amount );
auto conf = blind_transfer_help( from_blind_account_key_or_label,
from_blind_account_key_or_label,
blind_in, symbol, false, true );
FC_ASSERT( conf.outputs.size() > 0 );
auto to_account = my->get_account( to_account_id_or_name );
from_blind.to = to_account.id;
from_blind.amount = amount;
from_blind.blinding_factor = conf.outputs.back().decrypted_memo.blinding_factor;
from_blind.inputs.push_back( {conf.outputs.back().decrypted_memo.commitment, authority() } );
from_blind.fee = fees->calculate_fee( from_blind, asset_obj->options.core_exchange_rate );
idump( (from_blind) );
conf.trx.operations.push_back(from_blind);
ilog( "about to validate" );
conf.trx.validate();
ilog( "about to broadcast" );
conf.trx = sign_transaction( conf.trx, broadcast );
return conf;
} FC_CAPTURE_AND_RETHROW( (from_blind_account_key_or_label)(to_account_id_or_name)(amount_in)(symbol) ) }
blind_confirmation wallet_api::blind_transfer( string from_key_or_label,
string to_key_or_label,
string amount_in,
string symbol,
bool broadcast )
{
return blind_transfer_help( from_key_or_label, to_key_or_label, amount_in, symbol, broadcast, false );
}
blind_confirmation wallet_api::blind_transfer_help( string from_key_or_label,
string to_key_or_label,
string amount_in,
string symbol,
bool broadcast,
bool to_temp )
{
blind_confirmation confirm;
try {
FC_ASSERT( !is_locked() );
public_key_type from_key = get_public_key(from_key_or_label);
public_key_type to_key = get_public_key(to_key_or_label);
fc::optional<asset_object> asset_obj = get_asset(symbol);
FC_ASSERT(asset_obj.valid(), "Could not find asset matching ${asset}", ("asset", symbol));
blind_transfer_operation blind_tr;
blind_tr.outputs.resize(2);
auto fees = my->_remote_db->get_global_properties().parameters.current_fees;
auto amount = asset_obj->amount_from_string(amount_in);
asset total_amount = asset_obj->amount(0);
vector<fc::sha256> blinding_factors;
//auto from_priv_key = my->get_private_key( from_key );
blind_tr.fee = fees->calculate_fee( blind_tr, asset_obj->options.core_exchange_rate );
vector<commitment_type> used;
auto& to_asset_used_idx = my->_wallet.blind_receipts.get<by_to_asset_used>();
auto start = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,false) );
auto end = to_asset_used_idx.lower_bound( std::make_tuple(from_key,amount.asset_id,true) );
while( start != end )
{
auto result = my->_remote_db->get_blinded_balances( {start->commitment() } );
if( result.size() == 0 )
{
used.push_back( start->commitment() );
}
else
{
blind_tr.inputs.push_back({start->commitment(), start->control_authority});
blinding_factors.push_back( start->data.blinding_factor );
total_amount += start->amount;
if( total_amount >= amount + blind_tr.fee )
break;
}
++start;
}
for( const auto& u : used )
{
auto itr = my->_wallet.blind_receipts.get<by_commitment>().find( u );
my->_wallet.blind_receipts.modify( itr, []( blind_receipt& r ){ r.used = true; } );
}
FC_ASSERT( total_amount >= amount+blind_tr.fee, "Insufficent Balance", ("available",total_amount)("amount",amount)("fee",blind_tr.fee) );
auto one_time_key = fc::ecc::private_key::generate();
auto secret = one_time_key.get_shared_secret( to_key );
auto child = fc::sha256::hash( secret );
auto nonce = fc::sha256::hash( one_time_key.get_secret() );
auto blind_factor = fc::sha256::hash( child );
auto from_secret = one_time_key.get_shared_secret( from_key );
auto from_child = fc::sha256::hash( from_secret );
auto from_nonce = fc::sha256::hash( nonce );
auto change = total_amount - amount - blind_tr.fee;
fc::sha256 change_blind_factor;
fc::sha256 to_blind_factor;
if( change.amount > 0 )
{
idump(("to_blind_factor")(blind_factor) );
blinding_factors.push_back( blind_factor );
change_blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() - 1 );
wdump(("change_blind_factor")(change_blind_factor) );
}
else // change == 0
{
blind_tr.outputs.resize(1);
blind_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() );
idump(("to_sum_blind_factor")(blind_factor) );
blinding_factors.push_back( blind_factor );
idump(("nochange to_blind_factor")(blind_factor) );
}
fc::ecc::public_key from_pub_key = from_key;
fc::ecc::public_key to_pub_key = to_key;
blind_output to_out;
to_out.owner = to_temp ? authority() : authority( 1, public_key_type( to_pub_key.child( child ) ), 1 );
to_out.commitment = fc::ecc::blind( blind_factor, amount.amount.value );
idump(("to_out.blind")(blind_factor)(to_out.commitment) );
if( blind_tr.outputs.size() > 1 )
{
to_out.range_proof = fc::ecc::range_proof_sign( 0, to_out.commitment, blind_factor, nonce, 0, 0, amount.amount.value );
blind_output change_out;
change_out.owner = authority( 1, public_key_type( from_pub_key.child( from_child ) ), 1 );
change_out.commitment = fc::ecc::blind( change_blind_factor, change.amount.value );
change_out.range_proof = fc::ecc::range_proof_sign( 0, change_out.commitment, change_blind_factor, from_nonce, 0, 0, change.amount.value );
blind_tr.outputs[1] = change_out;
blind_confirmation::output conf_output;
conf_output.label = from_key_or_label;
conf_output.pub_key = from_key;
conf_output.decrypted_memo.from = from_key;
conf_output.decrypted_memo.amount = change;
conf_output.decrypted_memo.blinding_factor = change_blind_factor;
conf_output.decrypted_memo.commitment = change_out.commitment;
conf_output.decrypted_memo.check = from_secret._hash[0];
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
conf_output.confirmation.to = from_key;
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( from_secret, fc::raw::pack( conf_output.decrypted_memo ) );
conf_output.auth = change_out.owner;
conf_output.confirmation_receipt = conf_output.confirmation;
confirm.outputs.push_back( conf_output );
}
blind_tr.outputs[0] = to_out;
blind_confirmation::output conf_output;
conf_output.label = to_key_or_label;
conf_output.pub_key = to_key;
conf_output.decrypted_memo.from = from_key;
conf_output.decrypted_memo.amount = amount;
conf_output.decrypted_memo.blinding_factor = blind_factor;
conf_output.decrypted_memo.commitment = to_out.commitment;
conf_output.decrypted_memo.check = secret._hash[0];
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
conf_output.confirmation.to = to_key;
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );
conf_output.auth = to_out.owner;
conf_output.confirmation_receipt = conf_output.confirmation;
confirm.outputs.push_back( conf_output );
/** commitments must be in sorted order */
std::sort( blind_tr.outputs.begin(), blind_tr.outputs.end(),
[&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );
std::sort( blind_tr.inputs.begin(), blind_tr.inputs.end(),
[&]( const blind_input& a, const blind_input& b ){ return a.commitment < b.commitment; } );
confirm.trx.operations.emplace_back( std::move(blind_tr) );
ilog( "validate before" );
confirm.trx.validate();
confirm.trx = sign_transaction(confirm.trx, broadcast);
if( broadcast )
{
for( const auto& out : confirm.outputs )
try { receive_blind_transfer( out.confirmation_receipt, from_key_or_label, "" ); } catch ( ... ){}
}
return confirm;
} FC_CAPTURE_AND_RETHROW( (from_key_or_label)(to_key_or_label)(amount_in)(symbol)(broadcast)(confirm) ) }
/**
* Transfers a public balance from @from to one or more blinded balances using a
* stealth transfer.
*/
blind_confirmation wallet_api::transfer_to_blind( string from_account_id_or_name,
string asset_symbol,
/** map from key or label to amount */
map<string, string> to_amounts,
bool broadcast )
{ try {
FC_ASSERT( !is_locked() );
blind_confirmation confirm;
account_object from_account = my->get_account(from_account_id_or_name);
fc::optional<asset_object> asset_obj = get_asset(asset_symbol);
FC_ASSERT(asset_obj, "Could not find asset matching ${asset}", ("asset", asset_symbol));
transfer_to_blind_operation bop;
bop.from = from_account.id;
vector<fc::sha256> blinding_factors;
asset total_amount = asset_obj->amount(0);
for( auto item : to_amounts )
{
auto one_time_key = fc::ecc::private_key::generate();
auto to_key = get_public_key( item.first );
auto secret = one_time_key.get_shared_secret( to_key );
auto child = fc::sha256::hash( secret );
auto nonce = fc::sha256::hash( one_time_key.get_secret() );
auto blind_factor = fc::sha256::hash( child );
blinding_factors.push_back( blind_factor );
auto amount = asset_obj->amount_from_string(item.second);
total_amount += amount;
fc::ecc::public_key to_pub_key = to_key;
blind_output out;
out.owner = authority( 1, public_key_type( to_pub_key.child( child ) ), 1 );
out.commitment = fc::ecc::blind( blind_factor, amount.amount.value );
if( to_amounts.size() > 1 )
out.range_proof = fc::ecc::range_proof_sign( 0, out.commitment, blind_factor, nonce, 0, 0, amount.amount.value );
blind_confirmation::output conf_output;
conf_output.label = item.first;
conf_output.pub_key = to_key;
conf_output.decrypted_memo.amount = amount;
conf_output.decrypted_memo.blinding_factor = blind_factor;
conf_output.decrypted_memo.commitment = out.commitment;
conf_output.decrypted_memo.check = secret._hash[0];
conf_output.confirmation.one_time_key = one_time_key.get_public_key();
conf_output.confirmation.to = to_key;
conf_output.confirmation.encrypted_memo = fc::aes_encrypt( secret, fc::raw::pack( conf_output.decrypted_memo ) );
conf_output.confirmation_receipt = conf_output.confirmation;
confirm.outputs.push_back( conf_output );
bop.outputs.push_back(out);
}
bop.amount = total_amount;
bop.blinding_factor = fc::ecc::blind_sum( blinding_factors, blinding_factors.size() );
/** commitments must be in sorted order */
std::sort( bop.outputs.begin(), bop.outputs.end(),
[&]( const blind_output& a, const blind_output& b ){ return a.commitment < b.commitment; } );
confirm.trx.operations.push_back( bop );
my->set_operation_fees( confirm.trx, my->_remote_db->get_global_properties().parameters.current_fees);
confirm.trx.validate();
confirm.trx = sign_transaction(confirm.trx, broadcast);
if( broadcast )
{
for( const auto& out : confirm.outputs )
try { receive_blind_transfer( out.confirmation_receipt, "@"+from_account.name, "from @"+from_account.name ); } catch ( ... ){}
}
return confirm;
} FC_CAPTURE_AND_RETHROW( (from_account_id_or_name)(asset_symbol)(to_amounts) ) }
blind_receipt wallet_api::receive_blind_transfer( string confirmation_receipt, string opt_from, string opt_memo )
{
FC_ASSERT( !is_locked() );
stealth_confirmation conf(confirmation_receipt);
FC_ASSERT( conf.to );
blind_receipt result;
result.conf = conf;
auto to_priv_key_itr = my->_keys.find( *conf.to );
FC_ASSERT( to_priv_key_itr != my->_keys.end(), "No private key for receiver", ("conf",conf) );
auto to_priv_key = wif_to_key( to_priv_key_itr->second );
FC_ASSERT( to_priv_key );
auto secret = to_priv_key->get_shared_secret( conf.one_time_key );
auto child = fc::sha256::hash( secret );
auto child_priv_key = to_priv_key->child( child );
//auto blind_factor = fc::sha256::hash( child );
auto plain_memo = fc::aes_decrypt( secret, conf.encrypted_memo );
auto memo = fc::raw::unpack<stealth_confirmation::memo_data>( plain_memo );
result.to_key = *conf.to;
result.to_label = get_key_label( result.to_key );
if( memo.from )
{
result.from_key = *memo.from;
result.from_label = get_key_label( result.from_key );
if( result.from_label == string() )
{
result.from_label = opt_from;
set_key_label( result.from_key, result.from_label );
}
}
else
{
result.from_label = opt_from;
}
result.amount = memo.amount;
result.memo = opt_memo;
// confirm the amount matches the commitment (verify the blinding factor)
auto commtiment_test = fc::ecc::blind( memo.blinding_factor, memo.amount.amount.value );
FC_ASSERT( fc::ecc::verify_sum( {commtiment_test}, {memo.commitment}, 0 ) );
auto bbal = my->_remote_db->get_blinded_balances( {memo.commitment} );
FC_ASSERT( bbal.size(), "commitment not found in blockchain", ("memo",memo) );
blind_balance bal;
bal.amount = memo.amount;
bal.to = *conf.to;
if( memo.from ) bal.from = *memo.from;
bal.one_time_key = conf.one_time_key;
bal.blinding_factor = memo.blinding_factor;
bal.commitment = memo.commitment;
bal.used = false;
result.control_authority = bbal.front().owner;
result.data = memo;
auto child_key_itr = bbal.front().owner.key_auths.find( child_priv_key.get_public_key() );
if( child_key_itr != bbal.front().owner.key_auths.end() )
my->_keys[child_key_itr->first] = key_to_wif( child_priv_key );
// my->_wallet.blinded_balances[memo.amount.asset_id][bal.to].push_back( bal );
result.date = fc::time_point::now();
my->_wallet.blind_receipts.insert( result );
my->_keys[child_priv_key.get_public_key()] = key_to_wif( child_priv_key );
save_wallet_file();
return result;
}
vector<blind_receipt> wallet_api::blind_history( string key_or_account )
{
vector<blind_receipt> result;
auto pub_key = get_public_key( key_or_account );
if( pub_key == public_key_type() )
return vector<blind_receipt>();
for( auto& r : my->_wallet.blind_receipts )
{
if( r.from_key == pub_key || r.to_key == pub_key )
result.push_back( r );
}
std::sort( result.begin(), result.end(), [&]( const blind_receipt& a, const blind_receipt& b ){ return a.date > b.date; } );
return result;
}
} } // graphene::wallet
void fc::to_variant(const account_multi_index_type& accts, fc::variant& vo)
{
vo = vector<account_object>(accts.begin(), accts.end());
}
void fc::from_variant(const fc::variant& var, account_multi_index_type& vo)
{
const vector<account_object>& v = var.as<vector<account_object>>();
vo = account_multi_index_type(v.begin(), v.end());
}