Skip to content
This repository was archived by the owner on Aug 2, 2022. It is now read-only.

add cleos convert pack_transaction and unpack_transaction #5018

Closed
Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
125 changes: 122 additions & 3 deletions programs/cleos/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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'";
Expand Down Expand Up @@ -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));
}
}
}

Expand Down Expand Up @@ -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;
Copy link
Contributor

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


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]*[\{\[]");
Expand Down Expand Up @@ -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 ?
Copy link
Contributor

Choose a reason for hiding this comment

The 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"));
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure why anyone would use --unpack-action-data. What is the use case? I think it is fine, just not sure anyone would use it.

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 ?
Copy link
Contributor

Choose a reason for hiding this comment

The 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) ?
Copy link
Contributor

Choose a reason for hiding this comment

The 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();
Expand Down Expand Up @@ -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;
}
}
});


Expand Down