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

help messaging and transaction options to reduce duplicate transaction errors #422

Merged
Merged
Changes from all commits
Commits
File filter...
Filter file types
Jump to…
Jump to file or symbol
Failed to load files and symbols.

Always

Just for now

added options for expiration and forcing unique, started a help syste…

…m. ref #384
  • Loading branch information...
wanderingbort committed Sep 13, 2017
commit 0fc07fc9d5aebb7cc22e2958d05b05ddfe6ecfdc
@@ -1,4 +1,4 @@
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()
@@ -0,0 +1,129 @@
/*
* 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;
}

}}}
@@ -0,0 +1,29 @@
/*
* 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);
}}}
@@ -89,11 +89,15 @@ Usage: ./eosc create account [OPTIONS] creator name OwnerKey ActiveKey
#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;
}
ProTip! Use n and p to navigate between commits in a pull request.
You can’t perform that action at this time.