Fix CLI commands for creating witnesses, add commands for listing

witnesses, registering witness url.
Derive memo, witness, etc. keys from the active key.
Make witness_create_operation accept relative key identifiers.
Prevent wif_to_key from throwing on invalid base58 input.
Make witness_node accept witness keys in WIF format.
This commit is contained in:
Eric Frias 2015-06-30 17:28:07 -04:00
parent 6aa9264477
commit 9856d5e8fd
9 changed files with 561 additions and 62 deletions

View file

@ -274,10 +274,51 @@ namespace graphene { namespace app {
return {};
}
uint64_t database_api::get_witness_count()const
{
return _db.get_index_type<witness_index>().indices().size();
}
map<string, witness_id_type> database_api::lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const
{
FC_ASSERT( limit <= 1000 );
const auto& witnesses_by_id = _db.get_index_type<witness_index>().indices().get<by_id>();
// we want to order witnesses by account name, but that name is in the account object
// so the witness_index doesn't have a quick way to access it.
// get all the names and look them all up, sort them, then figure out what
// records to return. This could be optimized, but we expect the
// number of witnesses to be few and the frequency of calls to be rare
std::map<std::string, witness_id_type> witnesses_by_account_name;
for (const witness_object& witness : witnesses_by_id)
if (auto account_iter = _db.find(witness.witness_account))
if (account_iter->name >= lower_bound_name) // we can ignore anything below lower_bound_name
witnesses_by_account_name.insert(std::make_pair(account_iter->name, witness.id));
auto end_iter = witnesses_by_account_name.begin();
while (end_iter != witnesses_by_account_name.end() && limit--)
++end_iter;
witnesses_by_account_name.erase(end_iter, witnesses_by_account_name.end());
return witnesses_by_account_name;
}
vector<optional<witness_object>> database_api::get_witnesses(const vector<witness_id_type>& witness_ids)const
{
vector<optional<witness_object>> result; result.reserve(witness_ids.size());
std::transform(witness_ids.begin(), witness_ids.end(), std::back_inserter(result),
[this](witness_id_type id) -> optional<witness_object> {
if(auto o = _db.find(id))
return *o;
return {};
});
return result;
}
login_api::login_api(application& a)
:_app(a)
{
}
login_api::~login_api()
{
}
@ -466,7 +507,7 @@ namespace graphene { namespace app {
return fc::to_hex(fc::raw::pack(trx));
}
vector<operation_history_object> history_api::get_account_history(account_id_type account, operation_history_id_type stop, int limit, operation_history_id_type start) const
vector<operation_history_object> history_api::get_account_history(account_id_type account, operation_history_id_type stop, unsigned limit, operation_history_id_type start) const
{
FC_ASSERT(_app.chain_database());
const auto& db = *_app.chain_database();

View file

@ -193,6 +193,28 @@ namespace graphene { namespace app {
*/
fc::optional<witness_object> get_witness_by_account(account_id_type account)const;
/**
* @brief Get the total number of witnesses registered with the blockchain
*/
uint64_t get_witness_count()const;
/**
* @brief Get names and IDs for registered witnesses
* @param lower_bound_name Lower bound of the first name to return
* @param limit Maximum number of results to return -- must not exceed 1000
* @return Map of witness names to corresponding IDs
*/
map<string, witness_id_type> lookup_witness_accounts(const string& lower_bound_name, uint32_t limit)const;
/**
* @brief Get a list of witnesses by ID
* @param witness_ids IDs of the witnesses to retrieve
* @return The witnesses corresponding to the provided IDs
*
* This function has semantics identical to @ref get_objects
*/
vector<optional<witness_object>> get_witnesses(const vector<witness_id_type>& witness_ids)const;
/**
* @group Push Notification Methods
* These methods may be used to get push notifications whenever an object or market is changed
@ -295,7 +317,7 @@ namespace graphene { namespace app {
*/
vector<operation_history_object> get_account_history(account_id_type account,
operation_history_id_type stop = operation_history_id_type(),
int limit = 100,
unsigned limit = 100,
operation_history_id_type start = operation_history_id_type())const;
vector<bucket_object> get_market_history( asset_id_type a, asset_id_type b, uint32_t bucket_seconds,
@ -418,7 +440,10 @@ FC_API(graphene::app::database_api,
(get_settle_orders)
(list_assets)
(get_delegate_by_account)
(get_witnesses)
(get_witness_by_account)
(get_witness_count)
(lookup_witness_accounts)
(subscribe_to_objects)
(unsubscribe_from_objects)
(subscribe_to_market)

View file

@ -367,7 +367,7 @@ namespace graphene { namespace chain {
/// The account which owns the delegate. This account pays the fee for this operation.
account_id_type witness_account;
string url;
key_id_type block_signing_key;
object_id_type block_signing_key; // key_id_type or relative_key_id_type
secret_hash_type initial_secret;
account_id_type fee_payer()const { return witness_account; }

View file

@ -27,6 +27,8 @@ namespace graphene { namespace chain {
void_result witness_create_evaluator::do_evaluate( const witness_create_operation& op )
{ try {
FC_ASSERT(db().get(op.witness_account).is_lifetime_member());
object_id_type block_signing_key_id = get_relative_id(op.block_signing_key);
FC_ASSERT(block_signing_key_id.type() == key_object_type);
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -37,10 +39,14 @@ object_id_type witness_create_evaluator::do_apply( const witness_create_operatio
vote_id = p.get_next_vote_id(vote_id_type::witness);
});
object_id_type block_signing_key_object_id = get_relative_id(op.block_signing_key);
FC_ASSERT(block_signing_key_object_id.type() == key_object_type);
key_id_type block_signing_key_id(block_signing_key_object_id.instance());
const auto& new_witness_object = db().create<witness_object>( [&]( witness_object& obj ){
obj.witness_account = op.witness_account;
obj.vote_id = vote_id;
obj.signing_key = op.block_signing_key;
obj.signing_key = block_signing_key_id;
obj.next_secret = op.initial_secret;
obj.url = op.url;
});

View file

@ -22,6 +22,8 @@
#include <graphene/chain/key_object.hpp>
#include <graphene/time/time.hpp>
#include <graphene/utilities/key_conversion.hpp>
#include <fc/thread/thread.hpp>
using namespace graphene::witness_plugin;
@ -39,8 +41,8 @@ void witness_plugin::plugin_set_program_options(
("witness-id,w", bpo::value<vector<string>>()->composing()->multitoken(),
"ID of witness controlled by this node (e.g. \"1.7.0\", quotes are required, may specify multiple times)")
("private-key", bpo::value<vector<string>>()->composing()->multitoken()->
DEFAULT_VALUE_VECTOR(std::make_pair(chain::key_id_type(), fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan"))))),
"Tuple of [key ID, private key] (may specify multiple times)")
DEFAULT_VALUE_VECTOR(std::make_pair(chain::key_id_type(), graphene::utilities::key_to_wif(fc::ecc::private_key::regenerate(fc::sha256::hash(std::string("nathan")))))),
"Tuple of [key ID, WIF private key] (may specify multiple times)")
;
config_file_options.add(command_line_options);
}
@ -51,12 +53,35 @@ std::string witness_plugin::plugin_name()const
}
void witness_plugin::plugin_initialize(const boost::program_options::variables_map& options)
{
{ try {
_options = &options;
LOAD_VALUE_SET(options, "witness-id", _witnesses, chain::witness_id_type)
//Define a type T which doesn't have a comma, as I can't put a comma in a macro argument
using T = std::pair<chain::key_id_type,fc::ecc::private_key>;
LOAD_VALUE_SET(options, "private-key", _private_keys, T)
if( options.count("private-key") )
{
const std::vector<std::string> key_id_to_wif_pair_strings = options["private-key"].as<std::vector<std::string>>();
for (const std::string& key_id_to_wif_pair_string : key_id_to_wif_pair_strings)
{
auto key_id_to_wif_pair = graphene::app::dejsonify<std::pair<chain::key_id_type, std::string> >(key_id_to_wif_pair_string);
fc::optional<fc::ecc::private_key> private_key = graphene::utilities::wif_to_key(key_id_to_wif_pair.second);
if (!private_key)
{
// the key isn't in WIF format; see if they are still passing the old native private key format. This is
// just here to ease the transition, can be removed soon
try
{
private_key = fc::variant(key_id_to_wif_pair.second).as<fc::ecc::private_key>();
}
catch (const fc::exception&)
{
FC_THROW("Invalid WIF-format private key ${key_string}", ("key_string", key_id_to_wif_pair.second));
}
}
_private_keys[key_id_to_wif_pair.first] = *private_key;
}
}
}
catch (const fc::exception& e){ edump((e)); throw; }
}
void witness_plugin::plugin_startup()

View file

@ -37,7 +37,15 @@ std::string key_to_wif(const fc::ecc::private_key& key)
fc::optional<fc::ecc::private_key> wif_to_key( const std::string& wif_key )
{
std::vector<char> wif_bytes = fc::from_base58(wif_key);
std::vector<char> wif_bytes;
try
{
wif_bytes = fc::from_base58(wif_key);
}
catch (const fc::parse_error_exception&)
{
return fc::optional<fc::ecc::private_key>();
}
if (wif_bytes.size() < 5)
return fc::optional<fc::ecc::private_key>();
std::vector<char> key_bytes(wif_bytes.begin() + 1, wif_bytes.end() - 4);

View file

@ -86,7 +86,8 @@ struct wallet_data
// map of account_name -> base58_private_key for
// incomplete account regs
map<string, string> pending_account_registrations;
map<string, vector<string> > pending_account_registrations;
map<string, string> pending_witness_registrations;
string ws_server = "ws://localhost:8090";
string ws_user;
@ -258,7 +259,7 @@ class wallet_api
* @ingroup Transaction Builder API
*/
void replace_operation_in_builder_transaction(transaction_handle_type handle,
int operation_index,
unsigned operation_index,
const operation& new_op);
/**
* @ingroup Transaction Builder API
@ -812,15 +813,39 @@ class wallet_api
signed_transaction create_delegate(string owner_account,
bool broadcast = false);
/** Lists all witnesses registered in the blockchain.
* This returns a list of all account names that own witnesses, and the associated witness id,
* sorted by name. This lists witnesses whether they are currently voted in or not.
*
* Use the \c lowerbound and limit parameters to page through the list. To retrieve all witnesss,
* start by setting \c lowerbound to the empty string \c "", and then each iteration, pass
* the last witness name returned as the \c lowerbound for the next \c list_witnesss() call.
*
* @param lowerbound the name of the first witness to return. If the named witness does not exist,
* the list will start at the witness that comes after \c lowerbound
* @param limit the maximum number of witnesss to return (max: 1000)
* @returns a list of witnesss mapping witness names to witness ids
*/
map<string,witness_id_type> list_witnesses(const string& lowerbound, uint32_t limit);
/** Returns information about the given witness.
* @param witness_name_or_id the name or id of the witness account owner, or the id of the witness
* @returns the information about the witness stored in the block chain
*/
witness_object get_witness(string owner_account);
/** Creates a witness object owned by the given account.
*
* An account can have at most one witness object.
*
* @param owner_account the name or id of the account which is creating the witness
* @param url a URL to include in the witness record in the blockchain. Clients may
* display this when showing a list of witnesses. May be blank.
* @param broadcast true to broadcast the transaction on the network
* @returns the signed transaction registering a witness
*/
signed_transaction create_witness(string owner_account,
string url,
bool broadcast = false);
/** Vote for a given delegate.
@ -919,7 +944,7 @@ FC_REFLECT( graphene::wallet::wallet_data,
(my_accounts)
(cipher_keys)
(extra_keys)
(pending_account_registrations)
(pending_account_registrations)(pending_witness_registrations)
(ws_server)
(ws_user)
(ws_password)
@ -968,6 +993,8 @@ FC_API( graphene::wallet::wallet_api,
(settle_asset)
(whitelist_account)
(create_delegate)
(get_witness)
(list_witnesses)
(create_witness)
(vote_for_delegate)
(vote_for_witness)

View file

@ -24,6 +24,8 @@
#include <string>
#include <list>
#include <boost/range/adaptor/map.hpp>
#include <fc/io/fstream.hpp>
#include <fc/io/json.hpp>
#include <fc/io/stdio.hpp>
@ -33,6 +35,7 @@
#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/address.hpp>
@ -86,11 +89,19 @@ public:
void operator()(const asset_create_operation& op)const;
};
template< class T >
optional< T > maybe_id( const string& name_or_id )
template<class T>
optional<T> maybe_id( const string& name_or_id )
{
if( std::isdigit( name_or_id.front() ) )
return fc::variant(name_or_id).as<T>();
{
try
{
return fc::variant(name_or_id).as<T>();
}
catch (const fc::exception&)
{
}
}
return optional<T>();
}
@ -112,10 +123,8 @@ string address_to_shorthash( const address& addr )
return result;
}
fc::ecc::private_key derive_private_key(
const std::string& prefix_string,
int sequence_number
)
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);
@ -188,26 +197,47 @@ private:
{
auto it = _wallet.pending_account_registrations.find( account.name );
FC_ASSERT( it != _wallet.pending_account_registrations.end() );
if( !import_key( account.name, it->second ) )
{
// 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)
}
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);
graphene::chain::address witness_key_address = graphene::chain::address(witness_private_key->get_public_key());
std::vector<key_id_type> registered_key_ids = _remote_db->get_keys_for_address(witness_key_address);
for (const key_id_type& id : registered_key_ids)
_keys[id] = wif_key;
_wallet.pending_witness_registrations.erase(iter);
}
fc::mutex _resync_mutex;
void resync()
{
_resync_mutex.lock();
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
@ -216,27 +246,38 @@ private:
// notification is received, should also be done here
// "batch style" by querying the blockchain
if( _wallet.pending_account_registrations.size() > 0 )
if( !_wallet.pending_account_registrations.empty() )
{
std::vector<string> v_names;
v_names.reserve( _wallet.pending_account_registrations.size() );
std::transform(_wallet.pending_account_registrations.begin(), _wallet.pending_account_registrations.end(),
std::back_inserter(v_names), [](const std::pair<string, string>& p) {
return p.first;
});
// 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));
std::vector< fc::optional< graphene::chain::account_object >>
v_accounts = _remote_db->lookup_account_names( v_names );
// 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 );
for( fc::optional< graphene::chain::account_object > opt_account : v_accounts )
{
if( ! opt_account.valid() )
continue;
claim_registered_account(*opt_account);
}
// 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);
_resync_mutex.unlock();
// 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()
{
@ -430,6 +471,7 @@ public:
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 );
@ -440,10 +482,12 @@ public:
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(key_id_type id)const
{
auto it = _keys.find(id);
@ -453,6 +497,15 @@ public:
FC_ASSERT( privkey );
return *privkey;
}
fc::ecc::private_key get_private_key_for_account(const account_object& account)const
{
vector<key_id_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());
}
fc::ecc::public_key get_public_key(key_id_type id)const
{
vector<optional<key_object>> keys = _remote_db->get_keys( {id} );
@ -481,6 +534,8 @@ public:
if( item.first.type() == key_object_type )
keys.insert( item.first );
}
keys.insert(acnt.options.get_memo_key());
auto opt_keys = _remote_db->get_keys( vector<key_id_type>(keys.begin(),keys.end()) );
for( const fc::optional<key_object>& opt_key : opt_keys )
{
@ -574,7 +629,7 @@ public:
_builder_transactions[transaction_handle].operations.emplace_back(op);
}
void replace_operation_in_builder_transaction(transaction_handle_type handle,
int operation_index,
unsigned operation_index,
const operation& new_op)
{
FC_ASSERT(_builder_transactions.count(handle));
@ -738,6 +793,44 @@ public:
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();
graphene::chain::address derived_address(derived_public_key);
std::vector<key_id_type> registered_keys = _remote_db->get_keys_for_address(derived_address);
if (registered_keys.empty())
{
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,
@ -745,22 +838,25 @@ public:
bool broadcast = false,
bool save_wallet = true)
{ try {
fc::ecc::private_key active_privkey = derive_private_key( key_to_wif(owner_privkey), 0);
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 =
this->get_account( registrar_account );
account_object registrar_account_object = get_account( registrar_account );
account_id_type registrar_account_id = registrar_account_object.id;
account_object referrer_account_object =
this->get_account( referrer_account );
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;
@ -773,18 +869,23 @@ public:
active_key_create_op.fee_paying_account = registrar_account_id;
active_key_create_op.key_data = active_pubkey;
key_create_operation memo_key_create_op;
memo_key_create_op.fee_paying_account = registrar_account_id;
memo_key_create_op.key_data = memo_pubkey;
// key_create_op.calculate_fee(db.current_fee_schedule());
// TODO: Check if keys already exist!!!
relative_key_id_type owner_rkid(0);
relative_key_id_type active_rkid(1);
relative_key_id_type memo_rkid(2);
account_create_op.registrar = registrar_account_id;
account_create_op.name = account_name;
account_create_op.owner = authority(1, owner_rkid, 1);
account_create_op.active = authority(1, active_rkid, 1);
account_create_op.options.memo_key = active_rkid;
account_create_op.options.memo_key = memo_rkid;
// current_fee_schedule()
// find_account(pay_from_account)
@ -795,6 +896,7 @@ public:
tx.operations.push_back( owner_key_create_op );
tx.operations.push_back( active_key_create_op );
tx.operations.push_back( memo_key_create_op );
tx.operations.push_back( account_create_op );
tx.visit( operation_set_fee( _remote_db->get_global_properties().parameters.current_fees ) );
@ -817,7 +919,8 @@ public:
// we do not insert owner_privkey here because
// it is intended to only be used for key recovery
_wallet.pending_account_registrations[ account_name ] = key_to_wif( active_privkey );
_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 )
@ -1083,19 +1186,77 @@ public:
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) )
}
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();
key_create_operation witness_key_create_op;
witness_key_create_op.fee_paying_account = witness_account.id;
witness_key_create_op.key_data = witness_public_key;
relative_key_id_type witness_relative_key_id(0);
witness_create_operation witness_create_op;
witness_create_op.witness_account = get_account_id(owner_account);
witness_create_op.witness_account = witness_account.id;
witness_create_op.block_signing_key = witness_relative_key_id;
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_key_create_op );
tx.operations.push_back( witness_create_op );
tx.visit( operation_set_fee( _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) ) }
@ -1711,7 +1872,7 @@ void wallet_api::add_operation_to_builder_transaction(transaction_handle_type tr
my->add_operation_to_builder_transaction(transaction_handle, op);
}
void wallet_api::replace_operation_in_builder_transaction(transaction_handle_type handle, int operation_index, const operation& new_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);
}
@ -1917,10 +2078,21 @@ signed_transaction wallet_api::create_delegate(string owner_account,
return my->create_delegate(owner_account, 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);
}
witness_object wallet_api::get_witness(string owner_account)
{
return my->get_witness(owner_account);
}
signed_transaction wallet_api::create_witness(string owner_account,
string url,
bool broadcast /* = false */)
{
return my->create_witness(owner_account, broadcast);
return my->create_witness(owner_account, url, broadcast);
}
signed_transaction wallet_api::vote_for_delegate(string voting_account,

195
programs/cli_wallet/cookbook.md Executable file
View file

@ -0,0 +1,195 @@
# Graphene CLI Wallet Cookbook
### Running a Local Test Network
Right now, there is no public testnet, so the only way to test is to run your
own private network. To do this, launch a witness node to generate blocks. In
the directory where you built your graphene distribution:
````
cd programs/witness_node
# if you have previously run a witness node, you may need to remove the old blockchain.
# at this early stage, new commits often make it impossible to reuse an old database
# rm -r witness_node_data_dir
./witness_node --rpc-endpoint --enable-stale-production --witness-id \""1.7.0"\" \""1.7.1"\" \""1.7.2"\" \""1.7.3"\" \""1.7.4"\" \""1.7.5"\" \""1.7.6"\" \""1.7.7"\" \""1.7.8"\" \""1.7.9"\" --private-key "[\"1.2.0\",\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"]"
````
The initial genesis state has ten pre-configured delegates (1.7.0-9) that all
use the same private key to sign their blocks. Launching `witness_node` this
way allows you to act as all ten delegates.
Now, in a second window, launch a `cli_wallet` process to interact with the
network.
```
cd programs/cli_wallet
# similarly, if you have previously run a wallet, you may need to wipe out your
# old wallet
# rm wallet.json
./cli_wallet
```
Before doing anything with the new wallet, set a password and unlock the
wallet.
*Warning*: your passwords will be displayed on the screen.
```
new >>> set_password my_password
locked >>> unlock my_password
unlocked >>>
```
### Account Management
To create a new account, you will need to start with an existing account with
some of the CORE asset that will pay the transaction fee registering your new
account. The account paying this fee will be the *Registrar*.
In the initial genesis state, the account `nathan` has all of the money, so we
will use it as the registrar.
```
# first, import the private key to take ownership of the 'nathan' account
unlocked >>> import_key "nathan" 5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3
unlocked >>> list_my_accounts
[{
"id": "1.3.15",
...
"name": "nathan",
...
]
# before nathan can create other accounts, we need to upgrade it to a prime member.
unlocked >>> upgrade_account nathan true
unlocked >>> create_account_with_brain_key "this is the brain key for my account" my-account nathan nathan true
```
Like most methods in the wallet, `create_account_with_brain_key`'s last
parameter is the boolean `broadcast`. This parameter tells the wallet whether
you want to publish the transaction on the network immediately, which is
usually what you want to do. If you pass false, it will just create the
transaction and sign it, and display it on the console, but it wouldn't be sent
out onto the network. This could be used to build up a multi-sig transaction
and collect the other signatures offline, or it could be used to construct a
transaction in a offline cold wallet that you could put on a flash drive and
broadcast from a machine connected to the network. Here, we'll always pass
`true` for the `broadcast` parameter.
### Transferring Currency
Your newly-created account doesn't have any funds in it yet, the `nathan`
account still has all the money. To send some CORE from `nathan` to your
account, use the `transfer` command:
```
unlocked >>> transfer nathan my-account 10000 CORE "have some CORE" true
```
### Becoming a Witness
To become a witness and be able to produce blocks, you first need to create a
witness object that can be voted in.
Note: If you want to experiment with things that require voting, be aware that
votes are only tallied once per day at the maintenance interval. For testing,
it's helpful to change the `GRAPHENE_DEFAULT_MAINTENANCE_INTERVAL` in
`libraries/chain/include/graphene/chain/config.hpp` to, say, 10 minutes.
Before we get started, we can see the current list of witnesses voted in, which
will simply be the ten default witnesses:
```
unlocked >>> get_global_properties
...
"active_witnesses": [
"1.7.0",
"1.7.1",
"1.7.2",
"1.7.3",
"1.7.4",
"1.7.5",
"1.7.6",
"1.7.7",
"1.7.8",
"1.7.9"
],
...
```
Only lifetime members can become witnesses, so you must first upgrade to a
lifetime member. Upgrade and create our witness object.
```
unlocked >>> upgrade_account my-account true
unlocked >>> create_witness my-account "http://foo.bar.com/" true
{
"ref_block_num": 139,
"ref_block_prefix": 3692461913,
"relative_expiration": 3,
"operations": [[
21,{
"fee": {
"amount": 0,
"asset_id": "1.4.0"
},
"witness_account": "1.3.16",
"url": "http://foo.bar.com/",
"block_signing_key": "1.2.0",
"initial_secret": "00000000000000000000000000000000000000000000000000000000"
}
]
],
"signatures": [[
"1.2.23",
"1f2ad5597af2ac4bf7a50f1eef2db49c9c0f7616718776624c2c09a2dd72a0c53a26e8c2bc928f783624c4632924330fc03f08345c8f40b9790efa2e4157184a37"
]
],
"extra_signatures": []
}
```
Our witness is registered, but it can't produce blocks because nobody has voted
it in. You can see the current list of active witnesses with
`get_global_properties`:
```
unlocked >>> get_global_properties
{
"active_witnesses": [
"1.7.0",
"1.7.1",
"1.7.2",
"1.7.3",
"1.7.4",
"1.7.5",
"1.7.7",
"1.7.8",
"1.7.9"
],
...
```
Now, we should vote our witness in. Vote all of the shares in both
`my-account` and `nathan` in favor of your new witness.
```
unlocked >>> vote_for_witness my-account my-account true true
unlocked >>> vote_for_witness nathan my-account true true
```
Now we wait until the next maintenance interval.
`get_dynamic_global_properties` tells us when that will be in
`next_maintenance_time`. Once the next maintenance interval passes, run
`get_global_properties` again and you should see that your new witness has been
voted in.
Even though it's voted in, it isn't producing any blocks yet because we only
told the witness_node to produce blocks for 1.7.0 - 1.7.9 on the command line,
and it doesn't know the private key for the witness. Get the witness object to
find out its id and the key we need, then find the the private key.
Warning: `dump_private_keys` will display your keys unencrypted on the
terminal, don't do this with someone looking over your shoulder.
```
unlocked >>> get_witness my-account
{
"id": "1.7.10",
...
"signing_key": "1.2.25",
...
}
unlocked >>> dump_private_keys
[[
...
],[
"1.2.25",
"5JGi7DM7J8fSTizZ4D9roNgd8dUc5pirUe9taxYCUUsnvQ4zCaQ"
]
]
```
Now we need to re-start the witness, so shut down the wallet (ctrl-d), and
shut down the witness (ctrl-c). Re-launch the witness, now mentioning the new
witness 1.7.0 and its key 1.2.25:
```
./witness_node --rpc-endpoint --enable-stale-production --witness-id \""1.7.0"\" \""1.7.1"\" \""1.7.2"\" \""1.7.3"\" \""1.7.4"\" \""1.7.5"\" \""1.7.6"\" \""1.7.7"\" \""1.7.8"\" \""1.7.9"\" \""1.7.10"\" --private-key "[\"1.2.0\",\"5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3\"]" "[\"1.2.25\",\"5JGi7DM7J8fSTizZ4D9roNgd8dUc5pirUe9taxYCUUsnvQ4zCaQ\"]"
```