Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

CLI: Specify signing keys #2049

Merged
merged 8 commits into from
Nov 19, 2019
30 changes: 30 additions & 0 deletions libraries/wallet/include/graphene/wallet/wallet.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -376,6 +376,19 @@ class wallet_api
*/
signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true);

/**
* @ingroup Transaction Builder API
*
* Sign the transaction in a transaction builder and optionally broadcast to the network.
* @param transaction_handle handle of the transaction builder
* @param signing_keys Keys that must be used when signing the transaction
* @param broadcast whether to broadcast the signed transaction to the network
* @return a signed transaction
*/
signed_transaction sign_builder_transaction2(transaction_handle_type transaction_handle,
const vector<public_key_type>& signing_keys = vector<public_key_type>(),
bool broadcast = true);

/** Broadcast signed transaction
* @param tx signed transaction
* @returns the transaction ID along with the signed transaction.
Expand Down Expand Up @@ -1588,6 +1601,21 @@ class wallet_api
*/
signed_transaction sign_transaction(signed_transaction tx, bool broadcast = false);

/** Signs a transaction.
*
* Given a fully-formed transaction that is only lacking signatures, this signs
* the transaction with the inferred necessary keys and the explicitly provided keys,
* and optionally broadcasts the transaction
* @param tx the unsigned transaction
* @param signing_keys Keys that must be used when signing the transaction
* @param broadcast true if you wish to broadcast the transaction
* @return the signed version of the transaction
*/
signed_transaction sign_transaction2(signed_transaction tx,
const vector<public_key_type>& signing_keys = vector<public_key_type>(),
bool broadcast = true);


/** Get transaction signers.
*
* Returns information about who signed the transaction, specifically,
Expand Down Expand Up @@ -1769,6 +1797,7 @@ FC_API( graphene::wallet::wallet_api,
(set_fees_on_builder_transaction)
(preview_builder_transaction)
(sign_builder_transaction)
(sign_builder_transaction2)
(broadcast_transaction)
(propose_builder_transaction)
(propose_builder_transaction2)
Expand Down Expand Up @@ -1857,6 +1886,7 @@ FC_API( graphene::wallet::wallet_api,
(save_wallet_file)
(serialize_transaction)
(sign_transaction)
(sign_transaction2)
(add_transaction_signature)
(get_transaction_signers)
(get_key_references)
Expand Down
13 changes: 13 additions & 0 deletions libraries/wallet/wallet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -526,6 +526,13 @@ signed_transaction wallet_api::sign_builder_transaction(transaction_handle_type
return my->sign_builder_transaction(transaction_handle, broadcast);
}

signed_transaction wallet_api::sign_builder_transaction2(transaction_handle_type transaction_handle,
const vector<public_key_type>& explicit_keys,
bool broadcast)
{
return my->sign_builder_transaction2(transaction_handle, explicit_keys, broadcast);
}

pair<transaction_id_type,signed_transaction> wallet_api::broadcast_transaction(signed_transaction tx)
{
return my->broadcast_transaction(tx);
Expand Down Expand Up @@ -999,6 +1006,12 @@ signed_transaction wallet_api::sign_transaction(signed_transaction tx, bool broa
return my->sign_transaction( tx, broadcast);
} FC_CAPTURE_AND_RETHROW( (tx) ) }

signed_transaction wallet_api::sign_transaction2(signed_transaction tx, const vector<public_key_type>& signing_keys,
bool broadcast /* = false */)
{ try {
return my->sign_transaction2( tx, signing_keys, broadcast);
} FC_CAPTURE_AND_RETHROW( (tx) ) }

flat_set<public_key_type> wallet_api::get_transaction_signers(const signed_transaction &tx) const
{ try {
return my->get_transaction_signers(tx);
Expand Down
6 changes: 6 additions & 0 deletions libraries/wallet/wallet_api_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,9 @@ class wallet_api_impl
asset set_fees_on_builder_transaction(transaction_handle_type handle, string fee_asset = GRAPHENE_SYMBOL);
transaction preview_builder_transaction(transaction_handle_type handle);
signed_transaction sign_builder_transaction(transaction_handle_type transaction_handle, bool broadcast = true);
signed_transaction sign_builder_transaction2(transaction_handle_type transaction_handle,
const vector<public_key_type>& signing_keys = vector<public_key_type>(),
bool broadcast = true);

pair<transaction_id_type,signed_transaction> broadcast_transaction(signed_transaction tx);

Expand Down Expand Up @@ -325,6 +328,9 @@ class wallet_api_impl
bool broadcast );

signed_transaction sign_transaction(signed_transaction tx, bool broadcast = false);
signed_transaction sign_transaction2(signed_transaction tx,
const vector<public_key_type>& signing_keys = vector<public_key_type>(),
bool broadcast = false);

flat_set<public_key_type> get_transaction_signers(const signed_transaction &tx) const;

Expand Down
9 changes: 9 additions & 0 deletions libraries/wallet/wallet_builder.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,15 @@ namespace graphene { namespace wallet { namespace detail {
sign_transaction(_builder_transactions[transaction_handle], broadcast);
}

signed_transaction wallet_api_impl::sign_builder_transaction2(transaction_handle_type
transaction_handle, const vector<public_key_type>& signing_keys, bool broadcast)
{
FC_ASSERT(_builder_transactions.count(transaction_handle));

return _builder_transactions[transaction_handle] =
sign_transaction2(_builder_transactions[transaction_handle], signing_keys, broadcast);
}

signed_transaction wallet_api_impl::propose_builder_transaction( transaction_handle_type handle,
time_point_sec expiration, uint32_t review_period_seconds, bool broadcast)
{
Expand Down
11 changes: 11 additions & 0 deletions libraries/wallet/wallet_sign.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -311,9 +311,20 @@ namespace graphene { namespace wallet { namespace detail {
}

signed_transaction wallet_api_impl::sign_transaction( signed_transaction tx, bool broadcast )
{
return sign_transaction2(tx, {}, broadcast);
}

signed_transaction wallet_api_impl::sign_transaction2( signed_transaction tx,
const vector<public_key_type>& signing_keys, bool broadcast)
{
set<public_key_type> approving_key_set = get_owned_required_keys(tx);

// Add any explicit keys to the approving_key_set
for (const public_key_type& explicit_key : signing_keys) {
approving_key_set.insert(explicit_key);
}

auto dyn_props = get_dynamic_global_properties();
tx.set_reference_block( dyn_props.head_block_id );

Expand Down
175 changes: 160 additions & 15 deletions tests/cli/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -174,12 +174,51 @@ bool generate_block(std::shared_ptr<graphene::app::application> app, graphene::c
}
}

bool generate_block(std::shared_ptr<graphene::app::application> app)
bool generate_block(std::shared_ptr<graphene::app::application> app)
{
graphene::chain::signed_block returned_block;
return generate_block(app, returned_block);
}


signed_block generate_block(std::shared_ptr<graphene::app::application> app, uint32_t skip, const fc::ecc::private_key& key, int miss_blocks)
{
// skip == ~0 will skip checks specified in database::validation_steps
skip |= database::skip_undo_history_check;

auto db = app->chain_database();
auto block = db->generate_block(db->get_slot_time(miss_blocks + 1),
db->get_scheduled_witness(miss_blocks + 1),
key, skip);
db->clear_pending();
return block;
}


//////
// Generate blocks until the timestamp
//////
uint32_t generate_blocks(std::shared_ptr<graphene::app::application> app, fc::time_point_sec timestamp)
//uint32_t generate_blocks(fc::time_point_sec timestamp, bool miss_intermediate_blocks, uint32_t skip)
{
bool miss_intermediate_blocks = true;
fc::ecc::private_key committee_key = fc::ecc::private_key::regenerate(fc::sha256::hash(string("nathan")));
uint32_t skip = ~0;
auto db = app->chain_database();

if( miss_intermediate_blocks )
pmconrad marked this conversation as resolved.
Show resolved Hide resolved
{
generate_block(app);
auto slots_to_miss = db->get_slot_at_time(timestamp);
if( slots_to_miss <= 1 )
return 1;
--slots_to_miss;
generate_block(app, skip, committee_key, slots_to_miss);
return 2;
}
}


///////////
/// @brief Skip intermediate blocks, and generate a maintenance block
/// @param app the application
Expand Down Expand Up @@ -215,7 +254,8 @@ class client_connection
client_connection(
std::shared_ptr<graphene::app::application> app,
const fc::temp_directory& data_dir,
const int server_port_number
const int server_port_number,
const std::string custom_wallet_filename = "wallet.json"
)
{
wallet_data.chain_id = app->chain_database()->get_chain_id();
Expand All @@ -231,25 +271,18 @@ class client_connection
BOOST_CHECK(remote_login_api->login( wallet_data.ws_user, wallet_data.ws_password ) );

wallet_api_ptr = std::make_shared<graphene::wallet::wallet_api>(wallet_data, remote_login_api);
wallet_filename = data_dir.path().generic_string() + "/wallet.json";
wallet_filename = data_dir.path().generic_string() + "/" + custom_wallet_filename;
wallet_api_ptr->set_wallet_filename(wallet_filename);

wallet_api = fc::api<graphene::wallet::wallet_api>(wallet_api_ptr);

wallet_cli = std::make_shared<fc::rpc::cli>(GRAPHENE_MAX_NESTED_OBJECTS);
for( auto& name_formatter : wallet_api_ptr->get_result_formatters() )
wallet_cli->format_result( name_formatter.first, name_formatter.second );

boost::signals2::scoped_connection closed_connection(websocket_connection->closed.connect([=]{
cerr << "Server has disconnected us.\n";
wallet_cli->stop();
}));
(void)(closed_connection);
}
~client_connection()
{
// wait for everything to finish up
fc::usleep(fc::milliseconds(500));
wallet_cli->stop();
}
public:
fc::http::websocket_client websocket_client;
Expand Down Expand Up @@ -316,10 +349,6 @@ struct cli_fixture
~cli_fixture()
{
BOOST_TEST_MESSAGE("Cleanup cli_wallet::boost_fixture_test_case");

// wait for everything to finish up
fc::usleep(fc::seconds(1));

app1->shutdown();
#ifdef _WIN32
sockQuit();
Expand Down Expand Up @@ -495,6 +524,122 @@ BOOST_FIXTURE_TEST_CASE( cli_get_signed_transaction_signers, cli_fixture )
}
}


///////////////////////
// Wallet RPC
// Test adding an unnecessary signature to a transaction
///////////////////////
BOOST_FIXTURE_TEST_CASE(cli_sign_tx_with_unnecessary_signature, cli_fixture) {
try {
auto db = app1->chain_database();

account_object nathan_acct = con.wallet_api_ptr->get_account("nathan");
INVOKE(upgrade_nathan_account);

// Register Bob account
const auto bob_bki = con.wallet_api_ptr->suggest_brain_key();
con.wallet_api_ptr->register_account(
"bob", bob_bki.pub_key, bob_bki.pub_key, "nathan", "nathan", 0, true
);

// Register Charlie account
const graphene::wallet::brain_key_info charlie_bki = con.wallet_api_ptr->suggest_brain_key();
con.wallet_api_ptr->register_account(
"charlie", charlie_bki.pub_key, charlie_bki.pub_key, "nathan", "nathan", 0, true
);
const account_object &charlie_acc = con.wallet_api_ptr->get_account("charlie");

// Import Bob's key
BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bob_bki.wif_priv_key));

// Create transaction with a transfer operation from Nathan to Charlie
transfer_operation top;
top.from = nathan_acct.id;
top.to = charlie_acc.id;
top.amount = asset(5000);
top.fee = db->current_fee_schedule().calculate_fee(top);

signed_transaction test_tx;
test_tx.operations.push_back(top);

// Sign the transaction with the implied nathan's key and the explicitly yet unnecessary Bob's key
auto signed_trx = con.wallet_api_ptr->sign_transaction2(test_tx, {bob_bki.pub_key}, false);

// Check for two signatures on the transaction
BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 2);
flat_set<public_key_type> signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);

// Check that the signed transaction contains both Nathan's required signature and Bob's unnecessary signature
BOOST_CHECK_EQUAL(nathan_acct.active.get_keys().size(), 1);
flat_set<public_key_type> expected_signers = {bob_bki.pub_key, nathan_acct.active.get_keys().front()};
flat_set<public_key_type> actual_signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);
BOOST_CHECK(signers == expected_signers);

} catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
}
}


///////////////////////
// Wallet RPC
// Test adding an unnecessary signature to a transaction builder
///////////////////////
BOOST_FIXTURE_TEST_CASE(cli_sign_tx_builder_with_unnecessary_signature, cli_fixture) {
try {
auto db = app1->chain_database();

account_object nathan_acct = con.wallet_api_ptr->get_account("nathan");
INVOKE(upgrade_nathan_account);

// Register Bob account
const auto bob_bki = con.wallet_api_ptr->suggest_brain_key();
con.wallet_api_ptr->register_account(
"bob", bob_bki.pub_key, bob_bki.pub_key, "nathan", "nathan", 0, true
);

// Register Charlie account
const graphene::wallet::brain_key_info charlie_bki = con.wallet_api_ptr->suggest_brain_key();
con.wallet_api_ptr->register_account(
"charlie", charlie_bki.pub_key, charlie_bki.pub_key, "nathan", "nathan", 0, true
);
const account_object &charlie_acc = con.wallet_api_ptr->get_account("charlie");

// Import Bob's key
BOOST_CHECK(con.wallet_api_ptr->import_key("bob", bob_bki.wif_priv_key));

// Use transaction builder to build a transaction with a transfer operation from Nathan to Charlie
graphene::wallet::transaction_handle_type tx_handle = con.wallet_api_ptr->begin_builder_transaction();

transfer_operation top;
top.from = nathan_acct.id;
top.to = charlie_acc.id;
top.amount = asset(5000);

con.wallet_api_ptr->add_operation_to_builder_transaction(tx_handle, top);
con.wallet_api_ptr->set_fees_on_builder_transaction(tx_handle, GRAPHENE_SYMBOL);

// Sign the transaction with the implied nathan's key and the explicitly yet unnecessary Bob's key
auto signed_trx = con.wallet_api_ptr->sign_builder_transaction2(tx_handle, {bob_bki.pub_key}, false);

// Check for two signatures on the transaction
BOOST_CHECK_EQUAL(signed_trx.signatures.size(), 2);
flat_set<public_key_type> signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);

// Check that the signed transaction contains both Nathan's required signature and Bob's unnecessary signature
BOOST_CHECK_EQUAL(nathan_acct.active.get_keys().size(), 1);
flat_set<public_key_type> expected_signers = {bob_bki.pub_key, nathan_acct.active.get_keys().front()};
flat_set<public_key_type> actual_signers = con.wallet_api_ptr->get_transaction_signers(signed_trx);
BOOST_CHECK(signers == expected_signers);

} catch (fc::exception &e) {
edump((e.to_detail_string()));
throw;
}
}


pmconrad marked this conversation as resolved.
Show resolved Hide resolved
BOOST_FIXTURE_TEST_CASE( cli_get_available_transaction_signers, cli_fixture )
{
try
Expand Down