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:
parent
6aa9264477
commit
9856d5e8fd
9 changed files with 561 additions and 62 deletions
|
|
@ -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();
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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; }
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
});
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
195
programs/cli_wallet/cookbook.md
Executable 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\"]"
|
||||
```
|
||||
Loading…
Reference in a new issue