提交 a03b66ba 编写于 作者: D Daniel Larimer 提交者: GitHub

Merge pull request #422 from wanderingbort/feature/384-better-dupe-tx-messaging

help messaging and transaction options to reduce duplicate transaction errors
add_executable( eosc main.cpp httpc.cpp )
add_executable( eosc main.cpp httpc.cpp help_text.cpp )
if( UNIX AND NOT APPLE )
set(rt_library rt )
endif()
......
/*
* Copyright (c) 2017, Respective Authors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#include "help_text.hpp"
#include <regex>
#include <fc/variant.hpp>
const char* transaction_help_text_header = 1 + R"text(
An error occurred while submitting the transaction for this command!
)text";
const char* duplicate_transaction_help_text = 1 + R"text(
The transaction is a duplicate of one already pushed to the producers. If this
is an intentionally repeated transaction there are a few ways to resolve the
issue:
- wait for the next block
- combine duplicate transactions into a single transaction
- adjust the expiration time using the `--expiration <milliseconds>` option
- use the `--force-unique` option to add additional nonce data
Please note, this will consume more bandwidth than the base transaction
)text";
const char* missing_sigs_help_text = 1 + R"text(
The transaction requires permissions that could not be authorized by the wallet.
Missing authrizations:
- ${1}@${2}
Please make sure the proper keys are imported into an unlocked wallet and try again!
)text";
const char* unknown_account_help_text = 1 + R"text(
The transaction references an account which does not exist.
Unknown accounts:
- ${1}
Please check the account names and try again!
)text";
const char* missing_abi_help_text = 1 + R"text(
The ABI for action "${2}" on code account "${1}" is unknown.
The payload cannot be automatically serialized.
You can push an arbitrary transaction using the 'push transaction' subcommand
)text";
const char* unknown_wallet_help_text = 1 + R"text(
Unable to find a wallet named "${1}", are you sure you typed the name correctly?
)text";
const char* bad_wallet_password_help_text = 1 + R"text(
Invalid password for wallet named "${1}"
)text";
const char* locked_wallet_help_text = 1 + R"text(
The wallet named "${1}" is locked. Please unlock it and try again.
)text";
const std::vector<std::pair<const char*, std::vector<const char *>>> error_help_text {
{"Error\n: 3030011", {transaction_help_text_header, duplicate_transaction_help_text}},
{"Error\n: 3030002[^\\x00]*Transaction declares authority.*account\":\"([^\"]*)\",\"permission\":\"([^\"]*)\"", {transaction_help_text_header, missing_sigs_help_text}},
{"Account not found: ([\\S]*)", {transaction_help_text_header, unknown_account_help_text}},
{"Error\n: 303", {transaction_help_text_header}},
{"unknown key[^\\x00]*abi_json_to_bin.*code\":\"([^\"]*)\".*action\":\"([^\"]*)\"", {missing_abi_help_text}},
{"Unable to open file[^\\x00]*wallet/open.*postdata\":\"([^\"]*)\"", {unknown_wallet_help_text}},
{"AES error[^\\x00]*wallet/unlock.*postdata\":\\[\"([^\"]*)\"", {bad_wallet_password_help_text}},
{"Wallet is locked: ([\\S]*)", {locked_wallet_help_text}},
};
auto smatch_to_variant(const std::smatch& smatch) {
auto result = fc::mutable_variant_object();
for(size_t index = 0; index < smatch.size(); index++) {
auto name = boost::lexical_cast<std::string>(index);
if (smatch[index].matched) {
result = result(name, smatch.str(index));
} else {
result = result(name, "");
}
}
return result;
};
namespace eos { namespace client { namespace help {
bool print_help_text(const fc::exception& e) {
bool result = false;
auto detail_str = e.to_detail_string();
try {
for (const auto& candidate : error_help_text) {
auto expr = std::regex {candidate.first};
std::smatch matches;
if (std::regex_search(detail_str, matches, expr)) {
auto args = smatch_to_variant(matches);
for (const auto& msg: candidate.second) {
std::cerr << fc::format_string(msg, args) << std::endl;
}
result = true;
break;
}
}
} catch (const std::regex_error& e ) {
std::cerr << "Error locating help text: "<< e.code() << " " << e.what() << std::endl;
}
return result;
}
}}}
\ No newline at end of file
/*
* Copyright (c) 2017, Respective Authors.
*
* The MIT License
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
#pragma once
#include <fc/exception/exception.hpp>
namespace eos { namespace client { namespace help {
bool print_help_text(const fc::exception& e);
}}}
\ No newline at end of file
......@@ -89,11 +89,15 @@ Options:
#include <fc/io/fstream.hpp>
#include "CLI11.hpp"
#include "help_text.hpp"
#define format_output(format, ...) fc::format_string(format, fc::mutable_variant_object() __VA_ARGS__ )
using namespace std;
using namespace eos;
using namespace eos::chain;
using namespace eos::utilities;
using namespace eos::client::help;
string program = "eosc";
string host = "localhost";
......@@ -132,7 +136,6 @@ const string wallet_unlock = wallet_func_base + "/unlock";
const string wallet_import_key = wallet_func_base + "/import_key";
const string wallet_sign_trx = wallet_func_base + "/sign_transaction";
inline std::vector<Name> sort_names( std::vector<Name>&& names ) {
std::sort( names.begin(), names.end() );
auto itr = std::unique( names.begin(), names.end() );
......@@ -172,6 +175,30 @@ vector<uint8_t> assemble_wast( const std::string& wast ) {
}
}
auto tx_expiration = fc::microseconds(100);
bool tx_force_unique = false;
void add_standard_transaction_options(CLI::App* cmd) {
CLI::callback_t parse_exipration = [](CLI::results_t res) -> bool {
double value_ms;
if (res.size() == 0 || !CLI::detail::lexical_cast(res[0], value_ms)) {
return false;
}
tx_expiration = fc::microseconds(static_cast<uint64_t>(value_ms * 1000.0));
return true;
};
cmd->add_option("-x,--expiration", parse_exipration, "set the time in milliseconds before a transaction expires, defaults to 0.1ms");
cmd->add_flag("-f,--force-unique", tx_force_unique, "force the transaction to be unique. this will consume extra bandwidth and remove any protections against accidently issuing the same transaction multiple times");
}
std::string generate_nonce_string() {
return std::to_string(fc::time_point::now().time_since_epoch().count() % 1000000);
}
types::Message generate_nonce() {
return Message(N(eos),{}, N(nonce), generate_nonce_string());
}
vector<types::AccountPermission> get_account_permissions(const vector<string>& permissions) {
auto fixedPermissions = permissions | boost::adaptors::transformed([](const string& p) {
......@@ -218,7 +245,7 @@ void sign_transaction(SignedTransaction& trx) {
fc::variant push_transaction( SignedTransaction& trx, bool sign ) {
auto info = get_info();
trx.expiration = info.head_block_time + 100; //chain.head_block_time() + 100;
trx.expiration = info.head_block_time + tx_expiration; //chain.head_block_time() + 100;
transaction_set_reference_block(trx, info.head_block_id);
boost::sort( trx.scope );
......@@ -253,6 +280,9 @@ int main( int argc, char** argv ) {
app.add_option( "--wallet-host", wallet_host, "the host where eos-walletd is running", true );
app.add_option( "--wallet-port", wallet_port, "the port where eos-walletd is running", true );
bool verbose_errors = false;
app.add_flag( "-v,--verbose", verbose_errors, "output verbose messages on error");
// Create subcommand
auto create = app.add_subcommand("create", "Create various items, on and off the blockchain", false);
create->require_subcommand();
......@@ -548,14 +578,28 @@ int main( int argc, char** argv ) {
transfer->add_option("amount", amount, "The amount of EOS to send")->required();
transfer->add_option("memo", memo, "The memo for the transfer");
transfer->add_flag("-s,--skip-sign", skip_sign, "Specify that unlocked wallet keys should not be used to sign transaction");
add_standard_transaction_options(transfer);
transfer->set_callback([&] {
SignedTransaction trx;
trx.scope = sort_names({sender,recipient});
if (tx_force_unique) {
if (memo.size() == 0) {
// use the memo to add a nonce
memo = generate_nonce_string();
} else {
// add a nonce message
transaction_emplace_message(trx, generate_nonce());
}
}
transaction_emplace_message(trx, config::EosContractName,
vector<types::AccountPermission>{{sender,"active"}},
"transfer", types::transfer{sender, recipient, amount, memo});
auto info = get_info();
trx.expiration = info.head_block_time + 100; //chain.head_block_time() + 100;
trx.expiration = info.head_block_time + tx_expiration; //chain.head_block_time() + 100;
transaction_set_reference_block(trx, info.head_block_id);
if (!skip_sign) {
sign_transaction(trx);
......@@ -689,7 +733,7 @@ int main( int argc, char** argv ) {
types::newaccount{creator, newaccount, owner_auth,
active_auth, recovery_auth, deposit});
trx.expiration = info.head_block_time + 100;
trx.expiration = info.head_block_time + tx_expiration;
transaction_set_reference_block(trx, info.head_block_id);
batch.emplace_back(trx);
}
......@@ -720,7 +764,7 @@ int main( int argc, char** argv ) {
transaction_emplace_message(trx, config::EosContractName,
vector<types::AccountPermission>{{sender,"active"}},
"transfer", types::transfer{sender, recipient, amount, memo});
trx.expiration = info.head_block_time + 100;
trx.expiration = info.head_block_time + tx_expiration;
transaction_set_reference_block(trx, info.head_block_id);
batch.emplace_back(trx);
......@@ -757,7 +801,7 @@ int main( int argc, char** argv ) {
transaction_emplace_message(trx, config::EosContractName,
vector<types::AccountPermission>{{sender,"active"}},
"transfer", types::transfer{sender, recipient, amount, memo});
trx.expiration = info.head_block_time + 100;
trx.expiration = info.head_block_time + tx_expiration;
transaction_set_reference_block(trx, info.head_block_id);
batch.emplace_back(trx);
......@@ -806,6 +850,11 @@ int main( int argc, char** argv ) {
SignedTransaction trx;
transaction_emplace_serialized_message(trx, contract, action, accountPermissions,
result.get_object()["binargs"].as<Bytes>());
if (tx_force_unique) {
transaction_emplace_message(trx, generate_nonce());
}
for( const auto& scope : scopes ) {
vector<string> subscopes;
boost::split( subscopes, scope, boost::is_any_of( ", :" ) );
......@@ -841,14 +890,21 @@ int main( int argc, char** argv ) {
auto errorString = e.to_detail_string();
if (errorString.find("Connection refused") != string::npos) {
if (errorString.find(fc::json::to_string(port)) != string::npos) {
elog("Failed to connect to eosd at ${ip}:${port}; is eosd running?", ("ip", host)("port", port));
std::cerr << format_output("Failed to connect to eosd at ${ip}:${port}; is eosd running?", ("ip", host)("port", port)) << std::endl;
} else if (errorString.find(fc::json::to_string(wallet_port)) != string::npos) {
elog("Failed to connect to eos-walletd at ${ip}:${port}; is eos-walletd running?", ("ip", wallet_host)("port", wallet_port));
std::cerr << format_output("Failed to connect to eos-walletd at ${ip}:${port}; is eos-walletd running?", ("ip", wallet_host)("port", wallet_port)) << std::endl;
} else {
elog("Failed to connect with error: ${e}", ("e", e.to_detail_string()));
std::cerr << format_output("Failed to connect") << std::endl;
}
if (verbose_errors) {
elog("connect error: ${e}", ("e", errorString));
}
} else {
elog("Failed with error: ${e}", ("e", e.to_detail_string()));
// attempt to extract the error code if one is present
if (!print_help_text(e) || verbose_errors) {
elog("Failed with error: ${e}", ("e", e.to_detail_string()));
}
}
return 1;
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册