-
Notifications
You must be signed in to change notification settings - Fork 3.7k
add cleos convert pack_transaction and unpack_transaction #5018
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -158,6 +158,7 @@ auto tx_expiration = fc::seconds(30); | |
string tx_ref_block_num_or_id; | ||
bool tx_force_unique = false; | ||
bool tx_dont_broadcast = false; | ||
bool tx_return_packed = false; | ||
bool tx_skip_sign = false; | ||
bool tx_print_json = false; | ||
bool print_request = false; | ||
|
@@ -186,6 +187,7 @@ void add_standard_transaction_options(CLI::App* cmd, string default_permission = | |
cmd->add_flag("-s,--skip-sign", tx_skip_sign, localized("Specify if unlocked wallet keys should be used to sign transaction")); | ||
cmd->add_flag("-j,--json", tx_print_json, localized("print result as json")); | ||
cmd->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout)")); | ||
cmd->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction")); | ||
cmd->add_option("-r,--ref-block", tx_ref_block_num_or_id, (localized("set the reference block num or block id used for TAPOS (Transaction as Proof-of-Stake)"))); | ||
|
||
string msg = "An account and permission level to authorize, as in 'account@permission'"; | ||
|
@@ -302,7 +304,11 @@ fc::variant push_transaction( signed_transaction& trx, int32_t extra_kcpu = 1000 | |
if (!tx_dont_broadcast) { | ||
return call(push_txn_func, packed_transaction(trx, compression)); | ||
} else { | ||
return fc::variant(trx); | ||
if (!tx_return_packed) { | ||
return fc::variant(trx); | ||
} else { | ||
return fc::variant(packed_transaction(trx, compression)); | ||
} | ||
} | ||
} | ||
|
||
|
@@ -359,6 +365,27 @@ bytes variant_to_bin( const account_name& account, const action_name& action, co | |
} | ||
} | ||
|
||
fc::variant bin_to_variant( const account_name& account, const action_name& action, const bytes& action_args) { | ||
static unordered_map<account_name, std::vector<char> > abi_cache; | ||
auto it = abi_cache.find( account ); | ||
if ( it == abi_cache.end() ) { | ||
const auto result = call(get_raw_code_and_abi_func, fc::mutable_variant_object("account_name", account)); | ||
std::tie( it, std::ignore ) = abi_cache.emplace( account, result["abi"].as_blob().data ); | ||
//we also received result["wasm"], but we don't use it | ||
} | ||
const std::vector<char>& abi_v = it->second; | ||
|
||
abi_def abi; | ||
if( abi_serializer::to_abi(abi_v, abi) ) { | ||
abi_serializer abis( abi, fc::seconds(10) ); | ||
auto action_type = abis.get_action_type(action); | ||
FC_ASSERT(!action_type.empty(), "Unknown action ${action} in contract ${contract}", ("action", action)("contract", account)); | ||
return abis.binary_to_variant(action_type, action_args, fc::seconds(10)); | ||
} else { | ||
FC_ASSERT(false, "No ABI found for ${contract}", ("contract", account)); | ||
} | ||
} | ||
|
||
fc::variant json_from_file_or_string(const string& file_or_str, fc::json::parse_type ptype = fc::json::legacy_parser) | ||
{ | ||
regex r("^[ \t]*[\{\[]"); | ||
|
@@ -1725,6 +1752,86 @@ int main( int argc, char** argv ) { | |
// create account | ||
auto createAccount = create_account_subcommand( create, true /*simple*/ ); | ||
|
||
// convert subcommand | ||
auto convert = app.add_subcommand("convert", localized("Pack and unpack transactions"), false); // TODO also add converting action args based on abi from here ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. To address your TODO, I do think having the ability to specify an abi file to use would be nice. In that case it should NOT connect to nodeos or need to. |
||
convert->require_subcommand(); | ||
|
||
// pack transaction | ||
string plain_signed_transaction_json; | ||
auto pack_transaction = convert->add_subcommand("pack_transaction", localized("From plain signed json to packed form")); | ||
pack_transaction->add_option("transaction", plain_signed_transaction_json, localized("The plain signed json (string)"))->required(); | ||
pack_transaction->set_callback([&] { | ||
fc::variant trx_var; | ||
try { | ||
trx_var = json_from_file_or_string(plain_signed_transaction_json); | ||
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse plain transaction JSON '${data}'", ("data", plain_signed_transaction_json)) | ||
signed_transaction trx = trx_var.as<signed_transaction>(); | ||
std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; | ||
}); | ||
|
||
// unpack transaction | ||
string packed_transaction_json; | ||
bool unpack_action_data_flag = false; | ||
auto unpack_transaction = convert->add_subcommand("unpack_transaction", localized("From packed to plain signed json form")); | ||
unpack_transaction->add_option("transaction", packed_transaction_json, localized("The packed transaction json (string containing packed_trx and optionally compression fields)"))->required(); | ||
unpack_transaction->add_flag("--unpack-action-data", unpack_action_data_flag, localized("Upack all action datas within transaction and only return those, needs interaction with nodeos")); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not sure why anyone would use |
||
unpack_transaction->set_callback([&] { | ||
fc::variant trx_var; | ||
try { | ||
trx_var = json_from_file_or_string(packed_transaction_json); | ||
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse packed transaction JSON '${data}'", ("data", packed_transaction_json)) | ||
string trx_hex; | ||
try { | ||
trx_hex = trx_var["packed_trx"].as_string(); | ||
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Missing packed_trx field in provided JSON '${data}'", ("data", packed_transaction_json)) | ||
|
||
std::vector<char> trx_blob(trx_hex.size()/2); | ||
fc::from_hex(trx_hex, trx_blob.data(), trx_blob.size()); | ||
transaction unpacked_trx = fc::raw::unpack<transaction>(trx_blob); | ||
// TODO would it be nice to put the unpacked_trx actions into the trx actions in a separate unpacked_data field or somesuch ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Remove TODO, that would require api changes. |
||
if (unpack_action_data_flag) { | ||
for ( const auto& a : unpacked_trx.actions ) { | ||
fc::variant unpacked_action_data = bin_to_variant(a.account, a.name, a.data); | ||
std::cout << fc::json::to_pretty_string(unpacked_action_data) << std::endl; | ||
} | ||
} else { | ||
// TODO should the "sigantures" and "context_free_data" fields be copied from trx_var into the action field(s) ? | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. yes, signatures and context_free_data should be added to the signed transaction if available. |
||
std::cout << fc::json::to_pretty_string(fc::variant(unpacked_trx)) << std::endl; | ||
} | ||
}); | ||
|
||
// pack action data | ||
string unpacked_action_data_account_string; | ||
string unpacked_action_data_name_string; | ||
string unpacked_action_data_string; | ||
auto pack_action_data = convert->add_subcommand("pack_action_data", localized("From json action data to packed form")); | ||
pack_action_data->add_option("account", unpacked_action_data_account_string, localized("The name of the account that hosts the contract"))->required(); | ||
pack_action_data->add_option("name", unpacked_action_data_name_string, localized("The name of the function that's called by this action"))->required(); | ||
pack_action_data->add_option("unpacked_action_data", unpacked_action_data_string, localized("The action data expressed as json"))->required(); | ||
pack_action_data->set_callback([&] { | ||
fc::variant unpacked_action_data_json; | ||
try { | ||
unpacked_action_data_json = json_from_file_or_string(unpacked_action_data_string); | ||
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse unpacked action data JSON") | ||
bytes packed_action_data_string = variant_to_bin(unpacked_action_data_account_string, unpacked_action_data_name_string, unpacked_action_data_json); | ||
std::cout << fc::to_hex(packed_action_data_string.data(), packed_action_data_string.size()) << std::endl; | ||
}); | ||
|
||
// unpack action data | ||
string packed_action_data_account_string; | ||
string packed_action_data_name_string; | ||
string packed_action_data_string; | ||
auto unpack_action_data = convert->add_subcommand("unpack_action_data", localized("From packed to json action data form")); | ||
unpack_action_data->add_option("account", packed_action_data_account_string, localized("The name of the account that hosts the contract"))->required(); | ||
unpack_action_data->add_option("name", packed_action_data_name_string, localized("The name of the function that's called by this action"))->required(); | ||
unpack_action_data->add_option("packed_action_data", packed_action_data_string, localized("The action data expressed as packed hex string"))->required(); | ||
unpack_action_data->set_callback([&] { | ||
vector<char> packed_action_data_blob(packed_action_data_string.size()/2); | ||
fc::from_hex(packed_action_data_string, packed_action_data_blob.data(), packed_action_data_blob.size()); | ||
fc::variant unpacked_action_data_json = bin_to_variant(packed_action_data_account_string, packed_action_data_name_string, packed_action_data_blob); | ||
std::cout << fc::json::to_pretty_string(unpacked_action_data_json) << std::endl; | ||
}); | ||
|
||
// Get subcommand | ||
auto get = app.add_subcommand("get", localized("Retrieve various items and information from the blockchain"), false); | ||
get->require_subcommand(); | ||
|
@@ -2511,14 +2618,26 @@ int main( int argc, char** argv ) { | |
auto trxSubcommand = push->add_subcommand("transaction", localized("Push an arbitrary JSON transaction")); | ||
trxSubcommand->add_option("transaction", trx_to_push, localized("The JSON string or filename defining the transaction to push"))->required(); | ||
|
||
trxSubcommand->add_flag("-d,--dont-broadcast", tx_dont_broadcast, localized("don't broadcast transaction to the network (just print to stdout, essentially only validates)")); | ||
trxSubcommand->add_flag("--return-packed", tx_return_packed, localized("used in conjunction with --dont-broadcast to get the packed transaction")); | ||
|
||
trxSubcommand->set_callback([&] { | ||
fc::variant trx_var; | ||
try { | ||
trx_var = json_from_file_or_string(trx_to_push); | ||
} EOS_RETHROW_EXCEPTIONS(transaction_type_exception, "Fail to parse transaction JSON '${data}'", ("data",trx_to_push)) | ||
signed_transaction trx = trx_var.as<signed_transaction>(); | ||
auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); | ||
std::cout << fc::json::to_pretty_string(trx_result) << std::endl; | ||
|
||
if (!tx_dont_broadcast) { | ||
auto trx_result = call(push_txn_func, packed_transaction(trx, packed_transaction::none)); | ||
std::cout << fc::json::to_pretty_string(trx_result) << std::endl; | ||
} else { | ||
if (tx_return_packed) { | ||
std::cout << fc::json::to_pretty_string(fc::variant(packed_transaction(trx, packed_transaction::none))) << std::endl; | ||
} else { | ||
std::cout << fc::json::to_pretty_string(fc::variant(trx)) << std::endl; | ||
} | ||
} | ||
}); | ||
|
||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The above should be moved to its own function to avoid duplication and to use same abi_cache for variant_to_bin and bin_to_variant