提交 556d7d62 编写于 作者: D Daniel Larimer

Merge branch 'master' into dandev

......@@ -87,7 +87,7 @@ private-key = ["EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV","5KQwrPbw
plugin = eosio::producer_plugin
plugin = eosio::chain_api_plugin
plugin = eosio::wallet_api_plugin
plugin = eosio::account_history_api_plugin
plugin = eosio::history_api_plugin
plugin = eosio::http_plugin
# plugin = eosio::mongo_db_plugin
......
......@@ -111,13 +111,6 @@ void multisig::exec( account_name proposer, name proposal_name, account_name exe
ds >> trx_header;
eosio_assert( trx_header.expiration >= now(), "transaction expired" );
// Updating expiration is not necessary because the expiration field of a deferred transaction is modified to be valid anyway
/*
trx_header.expiration = now() + 60;
ds.seekp(0);
ds << trx_header;
*/
bytes packed_provided_approvals = pack(prop_it->provided_approvals);
auto res = ::check_transaction_authorization( prop_it->packed_transaction.data(), prop_it->packed_transaction.size(),
(const char*)0, 0,
......
......@@ -366,7 +366,7 @@ namespace eosiosystem {
eosio::transaction out;
out.actions.emplace_back( permission_level{ from, N(active) }, _self, N(refund), from );
out.delay_sec = refund_delay;
out.send( from, receiver );
out.send( from, receiver, true );
const auto& fromv = _voters.get( from );
......
......@@ -58,8 +58,7 @@ extern "C" {
*
* @{
*/
void send_deferred(const uint128_t& sender_id, account_name payer, const char *serialized_transaction, size_t size);
void send_deferred(const uint128_t& sender_id, account_name payer, const char *serialized_transaction, size_t size, uint32_t replace_existing = 0);
void cancel_deferred(const uint128_t& sender_id);
......
......@@ -42,9 +42,9 @@ namespace eosio {
public:
transaction(time exp = now() + 60) : transaction_header( exp ) {}
void send(uint64_t sender_id, account_name payer) const {
void send(uint64_t sender_id, account_name payer, bool replace_existing = false) const {
auto serialize = pack(*this);
send_deferred(sender_id, payer, serialize.data(), serialize.size());
send_deferred(sender_id, payer, serialize.data(), serialize.size(), replace_existing);
}
vector<action> context_free_actions;
......
......@@ -136,6 +136,7 @@ extern "C" {
WASM_TEST_HANDLER_EX(test_transaction, send_action_sender);
WASM_TEST_HANDLER(test_transaction, deferred_print);
WASM_TEST_HANDLER_EX(test_transaction, send_deferred_transaction);
WASM_TEST_HANDLER_EX(test_transaction, send_deferred_transaction_replace);
WASM_TEST_HANDLER(test_transaction, send_deferred_tx_with_dtt_action);
WASM_TEST_HANDLER(test_transaction, cancel_deferred_transaction);
WASM_TEST_HANDLER(test_transaction, send_cf_action);
......
......@@ -167,6 +167,7 @@ struct test_transaction {
static void send_action_sender(uint64_t receiver, uint64_t code, uint64_t action);
static void deferred_print();
static void send_deferred_transaction(uint64_t receiver, uint64_t code, uint64_t action);
static void send_deferred_transaction_replace(uint64_t receiver, uint64_t code, uint64_t action);
static void send_deferred_tx_with_dtt_action();
static void cancel_deferred_transaction();
static void send_cf_action();
......
......@@ -245,6 +245,15 @@ void test_transaction::send_deferred_transaction(uint64_t receiver, uint64_t, ui
trx.send( 0xffffffffffffffff, receiver );
}
void test_transaction::send_deferred_transaction_replace(uint64_t receiver, uint64_t, uint64_t) {
using namespace eosio;
auto trx = transaction();
test_action_action<N(testapi), WASM_TEST_ACTION("test_transaction", "deferred_print")> test_action;
trx.actions.emplace_back(vector<permission_level>{{N(testapi), N(active)}}, test_action);
trx.delay_sec = 2;
trx.send( 0xffffffffffffffff, receiver, true );
}
void test_transaction::send_deferred_tx_with_dtt_action() {
using namespace eosio;
dtt_action dtt_act;
......@@ -258,7 +267,7 @@ void test_transaction::send_deferred_tx_with_dtt_action() {
auto trx = transaction();
trx.actions.emplace_back(deferred_act);
trx.delay_sec = dtt_act.delay_sec;
trx.send( 0xffffffffffffffff, dtt_act.payer );
trx.send( 0xffffffffffffffff, dtt_act.payer, true );
}
......
......@@ -214,7 +214,7 @@ void apply_context::execute_context_free_inline( action&& a ) {
}
void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx ) {
void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing ) {
FC_ASSERT( trx.context_free_actions.size() == 0, "context free actions are not currently allowed in generated transactions" );
trx.expiration = control.pending_block_time() + fc::microseconds(999'999); // Rounds up to nearest second (makes expiration check unnecessary)
trx.set_reference_block(control.head_block_id()); // No TaPoS check necessary
......@@ -257,6 +257,7 @@ void apply_context::schedule_deferred_transaction( const uint128_t& sender_id, a
uint32_t trx_size = 0;
auto& d = control.db();
if ( auto ptr = d.find<generated_transaction_object,by_sender_id>(boost::make_tuple(receiver, sender_id)) ) {
EOS_ASSERT( replace_existing, deferred_tx_duplicate, "deferred transaction with the same sender_id and payer already exists" );
d.modify<generated_transaction_object>( *ptr, [&]( auto& gtx ) {
gtx.sender = receiver;
gtx.sender_id = sender_id;
......
......@@ -476,7 +476,7 @@ class apply_context {
void exec();
void execute_inline( action&& a );
void execute_context_free_inline( action&& a );
void schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx );
void schedule_deferred_transaction( const uint128_t& sender_id, account_name payer, transaction&& trx, bool replace_existing );
void cancel_deferred_transaction( const uint128_t& sender_id, account_name sender );
void cancel_deferred_transaction( const uint128_t& sender_id ) { cancel_deferred_transaction(sender_id, receiver); }
......
......@@ -156,6 +156,8 @@ namespace eosio { namespace chain {
3040007, "Invalid Reference Block" )
FC_DECLARE_DERIVED_EXCEPTION( tx_duplicate, transaction_exception,
3040008, "duplicate transaction" )
FC_DECLARE_DERIVED_EXCEPTION( deferred_tx_duplicate, transaction_exception,
3040009, "duplicate deferred transaction" )
FC_DECLARE_DERIVED_EXCEPTION( action_validate_exception, chain_exception,
......
......@@ -1250,11 +1250,11 @@ class transaction_api : public context_aware_api {
context.execute_context_free_inline(std::move(act));
}
void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr<char> data, size_t data_len ) {
void send_deferred( const uint128_t& sender_id, account_name payer, array_ptr<char> data, size_t data_len, uint32_t replace_existing) {
try {
transaction trx;
fc::raw::unpack<transaction>(data, data_len, trx);
context.schedule_deferred_transaction(sender_id, payer, std::move(trx));
context.schedule_deferred_transaction(sender_id, payer, std::move(trx), replace_existing);
} FC_CAPTURE_AND_RETHROW((fc::to_hex(data, data_len)));
}
......@@ -1754,7 +1754,7 @@ REGISTER_INTRINSICS(context_free_transaction_api,
REGISTER_INTRINSICS(transaction_api,
(send_inline, void(int, int) )
(send_context_free_inline, void(int, int) )
(send_deferred, void(int, int64_t, int, int) )
(send_deferred, void(int, int64_t, int, int, int32_t) )
(cancel_deferred, void(int) )
);
......
......@@ -45,6 +45,10 @@ using namespace eosio;
#define INVOKE_R_R(api_handle, call_name, in_param) \
auto result = api_handle.call_name(fc::json::from_string(body).as<in_param>());
#define INVOKE_R_R_R(api_handle, call_name, in_param0, in_param1) \
const auto& vs = fc::json::json::from_string(body).as<fc::variants>(); \
auto result = api_handle.call_name(vs.at(0).as<in_param0>(), vs.at(1).as<in_param1>());
#define INVOKE_R_R_R_R(api_handle, call_name, in_param0, in_param1, in_param2) \
const auto& vs = fc::json::json::from_string(body).as<fc::variants>(); \
auto result = api_handle.call_name(vs.at(0).as<in_param0>(), vs.at(1).as<in_param1>(), vs.at(2).as<in_param2>());
......@@ -88,6 +92,8 @@ void wallet_api_plugin::plugin_startup() {
INVOKE_V_R_R(wallet_mgr, unlock, std::string, std::string), 200),
CALL(wallet, wallet_mgr, import_key,
INVOKE_V_R_R(wallet_mgr, import_key, std::string, std::string), 201),
CALL(wallet, wallet_mgr, create_key,
INVOKE_R_R_R(wallet_mgr, create_key, std::string, std::string), 201),
CALL(wallet, wallet_mgr, list_wallets,
INVOKE_R_V(wallet_mgr, list_wallets), 200),
CALL(wallet, wallet_mgr, list_keys,
......
......@@ -151,6 +151,14 @@ class wallet_api
*/
bool import_key( string wif_key );
/** Creates a key within the wallet to be used to sign transactions by an account.
*
* example: create_key K1
*
* @param key_type the key type to create. May be empty to allow wallet to pick appropriate/"best" key type
*/
string create_key( string key_type );
std::shared_ptr<detail::wallet_api_impl> my;
void encrypt_keys();
};
......
......@@ -99,6 +99,14 @@ public:
/// @throws fc::exception if wallet not found or locked.
void import_key(const std::string& name, const std::string& wif_key);
/// Creates a key within the specified wallet.
/// Wallet must be opened and unlocked
/// @param name of the wallet to create key in
/// @param type of key to create
/// @throws fc::exception if wallet not found or locked, or if the wallet cannot create said type of key
/// @return The public key of the created key
string create_key(const std::string& name, const std::string& key_type);
private:
/// Verify timeout has not occurred and reset timeout if not.
/// Calls lock_all() if timeout has passed.
......
......@@ -150,6 +150,23 @@ public:
FC_ASSERT( !"Key already in wallet" );
}
string create_key(string key_type)
{
if(key_type.empty())
key_type = _default_key_type;
private_key_type priv_key;
if(key_type == "K1")
priv_key = fc::crypto::private_key::generate<fc::ecc::private_key_shim>();
else if(key_type == "R1")
priv_key = fc::crypto::private_key::generate<fc::crypto::r1::private_key_shim>();
else
FC_THROW_EXCEPTION(chain::wallet_exception, "Key type \"${kt}\" not supported by software wallet", ("kt", key_type));
import_key((string)priv_key);
return (string)priv_key.get_public_key();
}
bool load_wallet_file(string wallet_filename = "")
{
// TODO: Merge imported wallet with existing wallet,
......@@ -218,6 +235,7 @@ public:
mode_t _old_umask;
#endif
const string _wallet_filename_extension = ".wallet";
const string _default_key_type = "K1";
};
} } } // eosio::wallet::detail
......@@ -252,6 +270,15 @@ bool wallet_api::import_key(string wif_key)
return false;
}
string wallet_api::create_key(string key_type)
{
FC_ASSERT(!is_locked());
string ret = my->create_key(key_type);
save_wallet_file();
return ret;
}
bool wallet_api::load_wallet_file( string wallet_filename )
{
return my->load_wallet_file( wallet_filename );
......
......@@ -4,6 +4,7 @@
*/
#include <eosio/wallet_plugin/wallet_manager.hpp>
#include <eosio/chain/exceptions.hpp>
#include <boost/algorithm/string.hpp>
namespace eosio {
namespace wallet {
......@@ -46,7 +47,8 @@ std::string wallet_manager::create(const std::string& name) {
wallet->set_password(password);
wallet->set_wallet_filename(wallet_filename.string());
wallet->unlock(password);
wallet->import_key(eosio_key);
if(eosio_key.size())
wallet->import_key(eosio_key);
wallet->lock();
wallet->unlock(password);
......@@ -172,6 +174,20 @@ void wallet_manager::import_key(const std::string& name, const std::string& wif_
w->import_key(wif_key);
}
string wallet_manager::create_key(const std::string& name, const std::string& key_type) {
check_timeout();
if (wallets.count(name) == 0) {
EOS_THROW(chain::wallet_nonexistent_exception, "Wallet not found: ${w}", ("w", name));
}
auto& w = wallets.at(name);
if (w->is_locked()) {
EOS_THROW(chain::wallet_locked_exception, "Wallet is locked: ${w}", ("w", name));
}
string upper_key_type = boost::to_upper_copy<std::string>(key_type);
return w->create_key(upper_key_type);
}
chain::signed_transaction
wallet_manager::sign_transaction(const chain::signed_transaction& txn, const flat_set<public_key_type>& keys, const chain::chain_id_type& id) {
check_timeout();
......
......@@ -54,7 +54,6 @@ void wallet_plugin::plugin_initialize(const variables_map& options) {
}
if (options.count("eosio-key")) {
std::string eosio_wif_key = options.at("eosio-key").as<std::string>();
eosio_wif_key = fc::json::from_string(eosio_wif_key).as<std::string>();
wallet_manager_ptr->set_eosio_key(eosio_wif_key);
}
}
......
......@@ -59,6 +59,7 @@ namespace eosio { namespace client { namespace http {
const string wallet_lock_all = wallet_func_base + "/lock_all";
const string wallet_unlock = wallet_func_base + "/unlock";
const string wallet_import_key = wallet_func_base + "/import_key";
const string wallet_create_key = wallet_func_base + "/create_key";
const string wallet_sign_trx = wallet_func_base + "/sign_transaction";
const string keosd_stop = "/v1/keosd/stop";
......
......@@ -1879,6 +1879,18 @@ int main( int argc, char** argv ) {
std::cout << localized("imported private key for: ${pubkey}", ("pubkey", std::string(pubkey))) << std::endl;
});
// create a key within wallet
string wallet_create_key_type;
auto createKeyInWallet = wallet->add_subcommand("create_key", localized("Create private key within wallet"), false);
createKeyInWallet->add_option("-n,--name", wallet_name, localized("The name of the wallet to create key into"), true);
createKeyInWallet->add_option("key_type", wallet_create_key_type, localized("Key type to create (K1/R1)"), true)->set_type_name("K1/R1");
createKeyInWallet->set_callback([&wallet_name, &wallet_create_key_type] {
//an empty key type is allowed -- it will let the underlying wallet pick which type it prefers
fc::variants vs = {fc::variant(wallet_name), fc::variant(wallet_create_key_type)};
const auto& v = call(wallet_url, wallet_create_key, vs);
std::cout << localized("Created new private key with a public key of: ") << fc::json::to_pretty_string(v) << std::endl;
});
// list wallets
auto listWallet = wallet->add_subcommand("list", localized("List opened wallets, * = unlocked"), false);
listWallet->set_callback([] {
......
......@@ -73,6 +73,7 @@ BOOST_AUTO_TEST_CASE(wallet_manager_test)
if (fc::exists("test.wallet")) fc::remove("test.wallet");
if (fc::exists("test2.wallet")) fc::remove("test2.wallet");
if (fc::exists("testgen.wallet")) fc::remove("testgen.wallet");
constexpr auto key1 = "5JktVNHnRX48BUdtewU7N1CyL4Z886c42x7wYW7XhNWkDQRhdcS";
constexpr auto key2 = "5Ju5RTcVDo35ndtzHioPMgebvBM6LkJ6tvuU6LTNQv8yaz3ggZr";
......@@ -159,6 +160,31 @@ BOOST_AUTO_TEST_CASE(wallet_manager_test)
wm.set_timeout(chrono::seconds(0));
BOOST_CHECK_EQUAL(0, wm.list_keys().size());
wm.set_timeout(chrono::seconds(15));
wm.set_eosio_key("");
const string test_key_create_types[] = {"K1", "R1", "k1", ""};
for(const string& key_type_to_create : test_key_create_types) {
wm.create("testgen");
//check that the public key returned looks legit through a string conversion
// (would throw otherwise)
public_key_type create_key_pub(wm.create_key("testgen", key_type_to_create));
//now pluck out the private key from the wallet and see if the public key of said
// private key matches what was returned earlier from the create_key() call
private_key_type create_key_priv(wm.list_keys().cbegin()->second);
BOOST_CHECK_EQUAL((string)create_key_pub, (string)create_key_priv.get_public_key());
wm.lock("testgen");
BOOST_CHECK(fc::exists("testgen.wallet"));
fc::remove("testgen.wallet");
}
wm.create("testgen");
BOOST_CHECK_THROW(wm.create_key("testgen", "xxx"), chain::wallet_exception);
wm.lock("testgen");
BOOST_CHECK(fc::exists("test.wallet"));
BOOST_CHECK(fc::exists("test2.wallet"));
......
......@@ -879,13 +879,36 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
produce_blocks(10);
//schedule twice (second deferred transaction should replace first one)
//schedule twice without replace_existing flag (second deferred transaction should replace first one)
{
transaction_trace_ptr trace;
uint32_t count = 0;
auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; ++count; } } );
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {});
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {});
BOOST_CHECK_THROW(CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction", {}), deferred_tx_duplicate);
produce_blocks( 3 );
//check that only one deferred transaction executed
auto dtrxs = control->get_scheduled_transactions();
BOOST_CHECK_EQUAL(dtrxs.size(), 1);
for (const auto& trx: dtrxs) {
control->push_scheduled_transaction(trx, fc::time_point::maximum());
}
BOOST_CHECK_EQUAL(1, count);
BOOST_CHECK(trace);
BOOST_CHECK_EQUAL( 1, trace->action_traces.size() );
c.disconnect();
}
produce_blocks(10);
//schedule twice with replace_existing flag (second deferred transaction should replace first one)
{
transaction_trace_ptr trace;
uint32_t count = 0;
auto c = control->applied_transaction.connect([&]( const transaction_trace_ptr& t) { if (t && t->scheduled) { trace = t; ++count; } } );
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction_replace", {});
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_transaction_replace", {});
produce_blocks( 3 );
//check that only one deferred transaction executed
......@@ -966,7 +989,7 @@ BOOST_FIXTURE_TEST_CASE(deferred_transaction_tests, TESTER) { try {
("code", name(dtt_act3.deferred_account))
("type", name(dtt_act3.deferred_action))
("requirement", name(dtt_act3.permission_name)));
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act3));
CALL_TEST_FUNCTION(*this, "test_transaction", "send_deferred_tx_with_dtt_action", fc::raw::pack(dtt_act3)); //will replace existing transaction
// If we make testapi account to be priviledged account:
// - the deferred transaction will work no matter who is the payer
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册